diff --git a/Mozzila.IoT.WebThing.sln b/Mozzila.IoT.WebThing.sln index c968945..b009acd 100644 --- a/Mozzila.IoT.WebThing.sln +++ b/Mozzila.IoT.WebThing.sln @@ -22,16 +22,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Itens", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleThing", "sample\SampleThing\SampleThing.csproj", "{6FB673AA-FD52-4509-97C8-28572549F609}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mozilla.IoT.WebThing.AcceptanceTest", "test\Mozilla.IoT.WebThing.AcceptanceTest\Mozilla.IoT.WebThing.AcceptanceTest.csproj", "{0D709627-98FA-4A39-8631-90C982ADED44}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiThing", "sample\MultiThing\MultiThing.csproj", "{3CDFC9FB-F240-419A-800D-79C506CBDAE2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Github", "Github", "{425538CC-334D-4DB3-B529-48EA7CD778BF}" -ProjectSection(SolutionItems) = preProject - .github\workflows\pull-request.yml = .github\workflows\pull-request.yml - .github\workflows\build-master.yml = .github\workflows\build-master.yml - .github\workflows\release.yml = .github\workflows\release.yml -EndProjectSection + ProjectSection(SolutionItems) = preProject + .github\workflows\pull-request.yml = .github\workflows\pull-request.yml + .github\workflows\build-master.yml = .github\workflows\build-master.yml + .github\workflows\release.yml = .github\workflows\release.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mozilla.IoT.WebThing.Intregration.Test", "test\Mozilla.IoT.WebThing.Intregration.Test\Mozilla.IoT.WebThing.Intregration.Test.csproj", "{2BC55368-0F49-4D1A-999D-A236616EC80A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -82,18 +82,6 @@ Global {6FB673AA-FD52-4509-97C8-28572549F609}.Release|x64.Build.0 = Release|Any CPU {6FB673AA-FD52-4509-97C8-28572549F609}.Release|x86.ActiveCfg = Release|Any CPU {6FB673AA-FD52-4509-97C8-28572549F609}.Release|x86.Build.0 = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|x64.ActiveCfg = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|x64.Build.0 = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|x86.ActiveCfg = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Debug|x86.Build.0 = Debug|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|Any CPU.Build.0 = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x64.ActiveCfg = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x64.Build.0 = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x86.ActiveCfg = Release|Any CPU - {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x86.Build.0 = Release|Any CPU {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -106,13 +94,25 @@ Global {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x64.Build.0 = Release|Any CPU {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x86.ActiveCfg = Release|Any CPU {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x86.Build.0 = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|x64.ActiveCfg = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|x64.Build.0 = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|x86.ActiveCfg = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Debug|x86.Build.0 = Debug|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|Any CPU.Build.0 = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|x64.ActiveCfg = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|x64.Build.0 = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|x86.ActiveCfg = Release|Any CPU + {2BC55368-0F49-4D1A-999D-A236616EC80A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4999C9EF-BCC2-4252-A404-0161E655AAD9} = {F6436C38-AB0A-4D3F-8BA7-E2C0FA30D052} {63874B3F-6000-418D-BD19-C56A4D86B612} = {65C51E32-2901-4983-A238-0F931D9EB651} {6FB673AA-FD52-4509-97C8-28572549F609} = {370B1F76-EFE0-44D4-A395-59F5EF266112} - {0D709627-98FA-4A39-8631-90C982ADED44} = {65C51E32-2901-4983-A238-0F931D9EB651} {3CDFC9FB-F240-419A-800D-79C506CBDAE2} = {370B1F76-EFE0-44D4-A395-59F5EF266112} {425538CC-334D-4DB3-B529-48EA7CD778BF} = {E90FFA85-A210-450A-AA08-528D7F8962C2} + {2BC55368-0F49-4D1A-999D-A236616EC80A} = {65C51E32-2901-4983-A238-0F931D9EB651} EndGlobalSection EndGlobal diff --git a/src/Mozilla.IoT.WebThing/ActionCollection.cs b/src/Mozilla.IoT.WebThing/ActionCollection.cs deleted file mode 100644 index c656a04..0000000 --- a/src/Mozilla.IoT.WebThing/ActionCollection.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Mozilla.IoT.WebThing.Actions; - -namespace Mozilla.IoT.WebThing -{ - public class ActionCollection : IEnumerable - { - private readonly ConcurrentDictionary _actions; - - public event EventHandler? Change; - - public ActionCollection() - { - _actions = new ConcurrentDictionary(); - } - - public void Add(Guid id, ActionInfo actionInfo) - { - _actions.TryAdd(id, actionInfo); - - actionInfo.StatusChanged += OnStatusChange; - - var change = Change; - change?.Invoke(this, actionInfo); - } - - public bool TryGetValue(Guid id, out ActionInfo? action) - => _actions.TryGetValue(id, out action); - - public bool TryRemove(Guid id, out ActionInfo action) - { - var result =_actions.TryRemove(id, out action); - if (result && action != null) - { - - action.StatusChanged -= OnStatusChange; - } - - return result; - } - - private void OnStatusChange(object? sender, EventArgs args) - { - var change = Change; - change?.Invoke(this, (ActionInfo)sender); - } - - public IEnumerator GetEnumerator() - => _actions.Values.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/src/Mozilla.IoT.WebThing/ActionContext.cs b/src/Mozilla.IoT.WebThing/ActionContext.cs deleted file mode 100644 index 730c1cc..0000000 --- a/src/Mozilla.IoT.WebThing/ActionContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing -{ - public class ActionContext - { - public ActionContext(Type actionType) - { - ActionType = actionType ?? throw new ArgumentNullException(nameof(actionType)); - } - - public Type ActionType { get; } - public ActionCollection Actions { get; } = new ActionCollection(); - } -} diff --git a/src/Mozilla.IoT.WebThing/Actions/ActionCollection.cs b/src/Mozilla.IoT.WebThing/Actions/ActionCollection.cs new file mode 100644 index 0000000..eddbd32 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/ActionCollection.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions +{ + /// + /// Collection of + /// + public class ActionCollection : IEnumerable + { + private readonly ConcurrentDictionary _actions; + private readonly DictionaryInputConvert _inputConvert; + private readonly IActionInfoFactory _actionInfoFactory; + + /// + /// Event to when Status of changed. + /// + public event EventHandler? Change; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The . + public ActionCollection(DictionaryInputConvert inputConvert, IActionInfoFactory actionInfoFactory) + { + _actionInfoFactory = actionInfoFactory ?? throw new ArgumentNullException(nameof(actionInfoFactory)); + _inputConvert = inputConvert; + _actions = new ConcurrentDictionary(); + } + + /// + /// Try to add Action to collection. + /// + /// The to be convert to . + /// The created. + /// Return true if could convert and added on collection, otherwise return false. + public bool TryAdd(JsonElement element, [NotNullWhen(true)]out ActionInfo? info) + { + info = null; + Dictionary? inputValues = null; + if (element.TryGetProperty("input", out var inputProperty)) + { + if (inputProperty.ValueKind == JsonValueKind.Object + && !_inputConvert.TryConvert(inputProperty, out inputValues!)) + { + return false; + } + } + + inputValues ??= new Dictionary(); + + info = _actionInfoFactory.CreateActionInfo(inputValues!); + if (info == null) + { + return false; + } + + info.StatusChanged += OnStatusChange; + + return _actions.TryAdd(info.GetId(), info); + } + + /// + /// Try to get by Id. + /// + /// The id of . + /// The . + /// Return true if could get by Id, otherwise return false. + public bool TryGetValue(Guid id, [NotNullWhen(true)]out ActionInfo? action) + => _actions.TryGetValue(id, out action); + + /// + /// Try to remove by Id. + /// + /// The id of . + /// The . + /// Return true if could remove by Id, otherwise return false. + public bool TryRemove(Guid id, [NotNullWhen(true)]out ActionInfo? action) + { + var result =_actions.TryRemove(id, out action); + if (result && action != null) + { + action.StatusChanged -= OnStatusChange; + } + + return result; + } + + private void OnStatusChange(object? sender, EventArgs args) + { + var change = Change; + change?.Invoke(this, (ActionInfo)sender!); + } + + /// + /// + /// + /// + public IEnumerator GetEnumerator() + => _actions.Values.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/ActionInfo.cs b/src/Mozilla.IoT.WebThing/Actions/ActionInfo.cs index 3d35ca4..f8f7c43 100644 --- a/src/Mozilla.IoT.WebThing/Actions/ActionInfo.cs +++ b/src/Mozilla.IoT.WebThing/Actions/ActionInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -6,59 +6,108 @@ namespace Mozilla.IoT.WebThing.Actions { + /// + /// Action information to return in Web Socket and Web API. + /// public abstract class ActionInfo { + private readonly Guid _id = Guid.NewGuid(); + /// + /// The to cancel action when ask by . + /// protected CancellationTokenSource Source { get; } = new CancellationTokenSource(); - - internal Guid Id { get; } = Guid.NewGuid(); - internal Thing Thing { get; set; } = default!; - protected abstract string ActionName { get; } - - public string Href { get; internal set; } + internal Thing? Thing { get; set; } + + /// + /// The href of action. + /// + public string Href { get; set; } = string.Empty; + + /// + /// The time when action was requested. + /// public DateTime TimeRequested { get; } = DateTime.UtcNow; + + /// + /// The time when action was completed + /// public DateTime? TimeCompleted { get; private set; } = null; - public string Status { get; private set; } = "pending"; - + + private ActionStatus _status = ActionStatus.Pending; - public abstract bool IsValid(); + /// + /// The of action. + /// + public ActionStatus Status + { + get => _status; + private set + { + _status = value; + StatusChanged?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// To performance action executing. + /// + /// The associated with action to be executed. + /// The of scope to execute action. + /// The action executed or executing. protected abstract ValueTask InternalExecuteAsync(Thing thing, IServiceProvider provider); + + /// + /// To Execute action. + /// + /// The associated with action to be executed. + /// The of scope to execute action. + /// Execute task async. public async Task ExecuteAsync(Thing thing, IServiceProvider provider) { + Status = ActionStatus.Pending; var logger = provider.GetRequiredService>(); - logger.LogInformation("Going to execute {actionName}", ActionName); + logger.LogInformation("Going to execute {actionName}. [Thing: {thingName}]", GetActionName(), thing.Name); + Status = ActionStatus.Executing; - var status = StatusChanged; - - Status = "executing"; - - status?.Invoke(this, EventArgs.Empty); - try { await InternalExecuteAsync(thing, provider) .ConfigureAwait(false); - logger.LogInformation("{actionName} to executed", ActionName); + logger.LogInformation("{actionName} to executed. [Thing: {thingName}", GetActionName(), thing.Name); } catch (Exception e) { - logger.LogError(e,"Error to execute {actionName}", ActionName); + logger.LogError(e,"Error to execute {actionName}. [Thing: {thingName}", GetActionName(), thing.Name); } TimeCompleted = DateTime.UtcNow; - - Status = "completed"; - - status?.Invoke(this, EventArgs.Empty); - + Status = ActionStatus.Completed; } + + /// + /// The action name. + /// + /// + public abstract string GetActionName(); - internal string GetActionName() => ActionName; - + /// + /// The action Id. + /// + /// + public Guid GetId() + => _id; + + /// + /// To cancel action executing. + /// public void Cancel() => Source.Cancel(); + /// + /// The Status changed event. + /// public event EventHandler? StatusChanged; } } diff --git a/src/Mozilla.IoT.WebThing/Actions/ActionStatus.cs b/src/Mozilla.IoT.WebThing/Actions/ActionStatus.cs new file mode 100644 index 0000000..eb41c58 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/ActionStatus.cs @@ -0,0 +1,23 @@ +namespace Mozilla.IoT.WebThing.Actions +{ + /// + /// Action status + /// + public enum ActionStatus + { + /// + /// Waiting to be execute. + /// + Pending, + + /// + /// Executing action. + /// + Executing, + + /// + /// Action completed. + /// + Completed + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/DictionaryInputConvert.cs b/src/Mozilla.IoT.WebThing/Actions/DictionaryInputConvert.cs new file mode 100644 index 0000000..3b04af0 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/DictionaryInputConvert.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions +{ + /// + /// Convert to . + /// + public readonly struct DictionaryInputConvert + { + private readonly IReadOnlyDictionary _actionParameters; + + /// + /// Initialize a new instance of . + /// + /// The of with all action parameters. + public DictionaryInputConvert(IReadOnlyDictionary actionParameters) + { + _actionParameters = actionParameters ?? throw new ArgumentNullException(nameof(actionParameters)); + } + + /// + /// Try to convert to the to . + /// + /// The of input values + /// The . + /// Return true if all parameter is correct, otherwise return false. + public bool TryConvert(JsonElement element, [NotNullWhen(true)]out Dictionary? input) + { + input = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var properties in element.EnumerateObject()) + { + if (!_actionParameters.TryGetValue(properties.Name, out var @params)) + { + input = null; + return false; + } + + if (!@params.TryGetValue(properties.Value, out var value)) + { + input = null; + return false; + } + + if (!input.TryAdd(properties.Name, value)) + { + input = null; + return false; + } + } + + foreach (var (property, parameter) in _actionParameters) + { + if (!input.ContainsKey(property)) + { + if (!parameter.CanBeNull) + { + input = null; + return false; + } + + input.Add(property, null); + } + } + + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/IActionInfoFactory.cs b/src/Mozilla.IoT.WebThing/Actions/IActionInfoFactory.cs new file mode 100644 index 0000000..3e25b4f --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/IActionInfoFactory.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Mozilla.IoT.WebThing.Actions +{ + /// + /// Create new instance of based in value of . + /// + public interface IActionInfoFactory + { + /// + /// Create new instance of . + /// + /// The value of input. + /// New instance of . + ActionInfo CreateActionInfo(Dictionary values); + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/IActionParameter.cs b/src/Mozilla.IoT.WebThing/Actions/IActionParameter.cs new file mode 100644 index 0000000..49ac14e --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/IActionParameter.cs @@ -0,0 +1,23 @@ +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions +{ + /// + /// Represent parameter of action. + /// + public interface IActionParameter + { + /// + /// If this parameter accepts null values. + /// + bool CanBeNull { get; } + + /// + /// Try get value from . + /// + /// The . + /// The value inside of . + /// return true if value match with rules, otherwise return false. + bool TryGetValue(JsonElement element, out object? value); + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Boolean/ParameterBoolean.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Boolean/ParameterBoolean.cs new file mode 100644 index 0000000..4e21daf --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Boolean/ParameterBoolean.cs @@ -0,0 +1,45 @@ +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Boolean +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterBoolean : IActionParameter + { + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + public ParameterBoolean(bool isNullable) + { + CanBeNull = isNullable; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + value = null; + return true; + } + + switch (element.ValueKind) + { + case JsonValueKind.True: + value = true; + return true; + case JsonValueKind.False: + value = false; + return true; + default: + value = null; + return false; + } + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterByte.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterByte.cs new file mode 100644 index 0000000..b088e36 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterByte.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterByte : IActionParameter + { + private readonly byte? _minimum; + private readonly byte? _maximum; + private readonly byte? _multipleOf; + private readonly byte[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterByte(bool isNullable, byte? minimum, byte? maximum, byte? multipleOf, byte[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if(!element.TryGetByte(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDecimal.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDecimal.cs new file mode 100644 index 0000000..fc07352 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDecimal.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterDecimal : IActionParameter + { + private readonly decimal? _minimum; + private readonly decimal? _maximum; + private readonly decimal? _multipleOf; + private readonly decimal[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterDecimal(bool isNullable, decimal? minimum, decimal? maximum, decimal? multipleOf, decimal[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetDecimal(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDouble.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDouble.cs new file mode 100644 index 0000000..3199c9e --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterDouble.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterDouble : IActionParameter + { + private readonly double? _minimum; + private readonly double? _maximum; + private readonly double? _multipleOf; + private readonly double[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterDouble(bool isNullable, double? minimum, double? maximum, double? multipleOf, double[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetDouble(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterFloat.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterFloat.cs new file mode 100644 index 0000000..e9909db --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterFloat.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterFloat : IActionParameter + { + private readonly float? _minimum; + private readonly float? _maximum; + private readonly float? _multipleOf; + private readonly float[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterFloat(bool isNullable, float? minimum, float? maximum, float? multipleOf, float[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetSingle(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterInt.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterInt.cs new file mode 100644 index 0000000..c39ba2c --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterInt.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterInt : IActionParameter + { + private readonly int? _minimum; + private readonly int? _maximum; + private readonly int? _multipleOf; + private readonly int[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterInt(bool isNullable, int? minimum, int? maximum, int? multipleOf, int[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetInt32(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterLong.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterLong.cs new file mode 100644 index 0000000..cd07bf2 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterLong.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterLong : IActionParameter + { + private readonly long? _minimum; + private readonly long? _maximum; + private readonly long? _multipleOf; + private readonly long[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterLong(bool isNullable, long? minimum, long? maximum, long? multipleOf, long[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetInt64(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterSByte.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterSByte.cs new file mode 100644 index 0000000..3eae410 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterSByte.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterSByte : IActionParameter + { + private readonly sbyte? _minimum; + private readonly sbyte? _maximum; + private readonly sbyte? _multipleOf; + private readonly sbyte[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterSByte(bool isNullable, sbyte? minimum, sbyte? maximum, sbyte? multipleOf, sbyte[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetSByte(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterShort.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterShort.cs new file mode 100644 index 0000000..d190bc9 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterShort.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterShort : IActionParameter + { + private readonly short? _minimum; + private readonly short? _maximum; + private readonly short? _multipleOf; + private readonly short[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterShort(bool isNullable, short? minimum, short? maximum, short? multipleOf, short[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetInt16(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUInt.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUInt.cs new file mode 100644 index 0000000..d2838be --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUInt.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterUInt : IActionParameter + { + private readonly uint? _minimum; + private readonly uint? _maximum; + private readonly uint? _multipleOf; + private readonly uint[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterUInt(bool isNullable, uint? minimum, uint? maximum, uint? multipleOf, uint[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetUInt32(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterULong.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterULong.cs new file mode 100644 index 0000000..9874a54 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterULong.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterULong : IActionParameter + { + private readonly ulong? _minimum; + private readonly ulong? _maximum; + private readonly ulong? _multipleOf; + private readonly ulong[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterULong(bool isNullable, ulong? minimum, ulong? maximum, ulong? multipleOf, ulong[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetUInt64(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUShort.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUShort.cs new file mode 100644 index 0000000..24de251 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/Number/ParameterUShort.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.Number +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterUShort : IActionParameter + { + private readonly ushort? _minimum; + private readonly ushort? _maximum; + private readonly ushort? _multipleOf; + private readonly ushort[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this action parameter could have. + public ParameterUShort(bool isNullable, ushort? minimum, ushort? maximum, ushort? multipleOf, ushort[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return false; + } + + if (!element.TryGetUInt16(out var jsonValue)) + { + return false; + } + + if (_minimum.HasValue && jsonValue < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue > _maximum.Value) + { + return false; + } + + if (_multipleOf.HasValue && jsonValue % _multipleOf.Value != 0) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterChar.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterChar.cs new file mode 100644 index 0000000..877ce0a --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterChar.cs @@ -0,0 +1,61 @@ +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterChar : IActionParameter + { + + private readonly char[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The possible values this action parameter can have. + public ParameterChar(bool isNullable, char[]? enums) + { + CanBeNull = isNullable; + _enums = enums; + } + + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + var @string = element.GetString(); + + if (@string.Length != 1) + { + return false; + } + + var jsonValue = @string[0]; + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTime.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTime.cs new file mode 100644 index 0000000..9d067fb --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTime.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterDateTime : IActionParameter + { + private readonly DateTime[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The possible values this action parameter can have. + public ParameterDateTime(bool isNullable, DateTime[]? enums) + { + CanBeNull = isNullable; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + if (!element.TryGetDateTime(out var jsonValue)) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTimeOffset.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTimeOffset.cs new file mode 100644 index 0000000..f566f53 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterDateTimeOffset.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterDateTimeOffset : IActionParameter + { + private readonly DateTimeOffset[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The possible values this action parameter can have. + public ParameterDateTimeOffset(bool isNullable, DateTimeOffset[]? enums) + { + CanBeNull = isNullable; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + if (!element.TryGetDateTimeOffset(out var jsonValue)) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterEnum.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterEnum.cs new file mode 100644 index 0000000..47fabb7 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterEnum.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + /// + public readonly struct ParameterEnum : IActionParameter + { + private readonly Type _enumType; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The enum type. + public ParameterEnum(bool isNullable, Type enumType) + { + CanBeNull = isNullable; + _enumType = enumType; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + if(!Enum.TryParse(_enumType, element.GetString(), true, out var jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterGuid.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterGuid.cs new file mode 100644 index 0000000..723e377 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterGuid.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterGuid : IActionParameter + { + private readonly Guid[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The possible values this action parameter can have. + public ParameterGuid(bool isNullable, Guid[]? enums) + { + CanBeNull = isNullable; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + if (!element.TryGetGuid(out var jsonValue)) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterString.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterString.cs new file mode 100644 index 0000000..b62186d --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterString.cs @@ -0,0 +1,77 @@ +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterString : IActionParameter + { + private readonly int? _minimum; + private readonly int? _maximum; + private readonly string[]? _enums; + private readonly Regex? _pattern; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepted null value. + /// The minimum length of string to be assign. + /// The maximum length of string to be assign. + /// The pattern of string to be assign. + /// The possible values this action parameter could have. + public ParameterString(bool isNullable, int? minimum, int? maximum, string? pattern, string[]? enums) + { + CanBeNull = isNullable; + _minimum = minimum; + _maximum = maximum; + _enums = enums; + _pattern = pattern != null ? new Regex(pattern, RegexOptions.Compiled) : null; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + var jsonValue = element.GetString(); + + if (_minimum.HasValue && jsonValue.Length < _minimum.Value) + { + return false; + } + + if (_maximum.HasValue && jsonValue.Length > _maximum.Value) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + if (_pattern != null && !_pattern.IsMatch(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterTimeSpan.cs b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterTimeSpan.cs new file mode 100644 index 0000000..86ed1f2 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Actions/Parameters/String/ParameterTimeSpan.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Actions.Parameters.String +{ + /// + /// Represent action parameter. + /// + public readonly struct ParameterTimeSpan : IActionParameter + { + private readonly TimeSpan[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// If action parameter accepts null value. + /// The possible values this action parameter can have. + public ParameterTimeSpan(bool isNullable, TimeSpan[]? enums) + { + CanBeNull = isNullable; + _enums = enums; + } + + /// + public bool CanBeNull { get; } + + /// + public bool TryGetValue(JsonElement element, out object? value) + { + value = null; + if (CanBeNull && element.ValueKind == JsonValueKind.Null) + { + return true; + } + + if (element.ValueKind != JsonValueKind.String) + { + return false; + } + + if (!TimeSpan.TryParse(element.GetString(), out var jsonValue)) + { + return false; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(jsonValue)) + { + return false; + } + + value = jsonValue; + return true; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Actions/Status.cs b/src/Mozilla.IoT.WebThing/Actions/Status.cs deleted file mode 100644 index 07ab422..0000000 --- a/src/Mozilla.IoT.WebThing/Actions/Status.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Mozilla.IoT.WebThing.Actions -{ - public enum Status - { - Pending, - Executing, - Completed - } -} diff --git a/src/Mozilla.IoT.WebThing/Assembly.cs b/src/Mozilla.IoT.WebThing/Assembly.cs index 2d74457..b03ccc4 100644 --- a/src/Mozilla.IoT.WebThing/Assembly.cs +++ b/src/Mozilla.IoT.WebThing/Assembly.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Mozilla.IoT.WebThing.Test")] +[assembly: InternalsVisibleTo("Mozilla.IoT.WebThing.Intregration.Test")] diff --git a/src/Mozilla.IoT.WebThing/Attributes/ThingActionAttribute.cs b/src/Mozilla.IoT.WebThing/Attributes/ThingActionAttribute.cs index 329d5ec..5c817d9 100644 --- a/src/Mozilla.IoT.WebThing/Attributes/ThingActionAttribute.cs +++ b/src/Mozilla.IoT.WebThing/Attributes/ThingActionAttribute.cs @@ -2,13 +2,35 @@ namespace Mozilla.IoT.WebThing.Attributes { - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + /// + /// Action information. + /// + [AttributeUsage(AttributeTargets.Method)] public class ThingActionAttribute : Attribute { + /// + /// If action should be ignore. + /// public bool Ignore { get; set; } + + /// + /// Custom action name. + /// public string? Name { get; set; } + + /// + /// Action title. + /// public string? Title { get; set; } + + /// + /// Action description. + /// public string? Description { get; set; } + + /// + /// Action types + /// public string[]? Type { get; set; } } } diff --git a/src/Mozilla.IoT.WebThing/Attributes/ThingEventAttribute.cs b/src/Mozilla.IoT.WebThing/Attributes/ThingEventAttribute.cs index d9fa623..e80ee90 100644 --- a/src/Mozilla.IoT.WebThing/Attributes/ThingEventAttribute.cs +++ b/src/Mozilla.IoT.WebThing/Attributes/ThingEventAttribute.cs @@ -2,14 +2,40 @@ namespace Mozilla.IoT.WebThing.Attributes { - [AttributeUsage(AttributeTargets.Event, AllowMultiple = false)] + /// + /// Event information. + /// + [AttributeUsage(AttributeTargets.Event)] public class ThingEventAttribute : Attribute { + /// + /// If event should be ignore. + /// public bool Ignore { get; set; } + + /// + /// Custom event name. + /// public string? Name { get; set; } + + /// + /// Event title. + /// public string? Title { get; set; } + + /// + /// Event description. + /// public string? Description { get; set; } + + /// + /// Event types + /// public string[]? Type { get; set; } + + /// + /// Unit of event + /// public string? Unit { get; set; } } } diff --git a/src/Mozilla.IoT.WebThing/Attributes/ThingParameterAttribute.cs b/src/Mozilla.IoT.WebThing/Attributes/ThingParameterAttribute.cs index 5d87c1c..7e3b68a 100644 --- a/src/Mozilla.IoT.WebThing/Attributes/ThingParameterAttribute.cs +++ b/src/Mozilla.IoT.WebThing/Attributes/ThingParameterAttribute.cs @@ -2,16 +2,36 @@ namespace Mozilla.IoT.WebThing.Attributes { - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] + /// + /// Action parameter information. + /// + [AttributeUsage(AttributeTargets.Parameter)] public class ThingParameterAttribute : Attribute { + /// + /// Action parameter name. + /// + public string? Name { get; set; } + + /// + /// Action parameter title. + /// public string? Title { get; set; } + + /// + /// Action parameter description. + /// public string? Description { get; set; } + + /// + /// Unit of Action parameter. + /// public string? Unit { get; set; } internal double? MinimumValue { get; private set; } /// - /// Validates only if the instance is greater than or exactly equal to "minimum" + /// Minimum accepts value. + /// This property should be use only for number(int, long, double, byte and etc). /// public double Minimum { @@ -22,7 +42,8 @@ public double Minimum internal double? MaximumValue { get; private set; } /// - /// Validates only if the instance is less than or exactly equal to "maximum" + /// Maximum accepts value. + /// This property should be use only for number(int, long, double, byte and etc). /// public double Maximum { @@ -33,7 +54,8 @@ public double Maximum internal int? MultipleOfValue { get; set; } /// - /// Valid only if it has a value strictly less than (not equal to) "exclusiveMaximum". + /// Multiple of accepts value. + /// This property should be use only for number(int, long, double, byte and etc). /// public int MultipleOf { @@ -44,7 +66,8 @@ public int MultipleOf internal double? ExclusiveMinimumValue { get; set; } /// - /// Valid only if it has a value strictly less than (not equal to) "exclusiveMaximum". + /// Exclusive minimum (less than and not equal) accepts value. + /// This property should be use only for number(int, long, double, byte and etc). /// public double ExclusiveMinimum { @@ -55,7 +78,8 @@ public double ExclusiveMinimum internal double? ExclusiveMaximumValue { get; set; } /// - /// Valid only if it has a value strictly greater than (not equal to) "exclusiveMinimum" + /// Exclusive maximum (great than and not equal) accepts value. + /// This property should be use only for number(int, long, double, byte and etc). /// public double ExclusiveMaximum { @@ -64,20 +88,40 @@ public double ExclusiveMaximum } - internal uint? MinimumLengthValue { get; set; } - public uint MinimumLength + internal int? MinimumLengthValue { get; set; } + + /// + /// Minimum string length accepts. + /// This property should be use only for string. + /// + public int MinimumLength { get => MinimumLengthValue.GetValueOrDefault(); set => MinimumLengthValue = value; } - internal uint? MaximumLengthValue { get; set; } - public uint MaximumLength + internal int? MaximumLengthValue { get; set; } + + /// + /// Maximum string length accepts. + /// This property should be use only for string. + /// + public int MaximumLength { get => MaximumLengthValue.GetValueOrDefault(); set => MaximumLengthValue = value; } + + /// + /// Pattern this action parameter must have. + /// This property should be use only for string. + /// public string? Pattern { get; set; } + + /// + /// Possible value this action parameter should have. + /// + public object[]? Enum { get; set; } } } diff --git a/src/Mozilla.IoT.WebThing/Attributes/ThingPropertyAttribute.cs b/src/Mozilla.IoT.WebThing/Attributes/ThingPropertyAttribute.cs index 5723531..3d0eb03 100644 --- a/src/Mozilla.IoT.WebThing/Attributes/ThingPropertyAttribute.cs +++ b/src/Mozilla.IoT.WebThing/Attributes/ThingPropertyAttribute.cs @@ -2,26 +2,71 @@ namespace Mozilla.IoT.WebThing.Attributes { - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + /// + /// Property information. + /// + [AttributeUsage(AttributeTargets.Property)] public class ThingPropertyAttribute : Attribute { + /// + /// Custom property name. + /// public string? Name { get; set; } + + /// + /// If property should be ignore. + /// public bool Ignore { get; set; } + + /// + /// Property title. + /// public string? Title { get; set; } + + /// + /// Property description. + /// public string? Description { get; set; } + + /// + /// Unit of property value. + /// public string? Unit { get; set; } + + /// + /// Property types. + /// public string[]? Type { get; set; } + + /// + /// If property is read-only. + /// public bool IsReadOnly { get; set; } + + /// + /// If property is write-only. + /// internal bool? IsWriteOnlyValue { get; set; } + /// + /// If property is write-only. + /// public bool IsWriteOnly { get => IsWriteOnlyValue.GetValueOrDefault(); set => IsWriteOnlyValue = value; } + /// + /// Possible value this property should have. + /// public object[]? Enum { get; set; } internal double? MinimumValue { get; set; } + + /// + /// Minimum accepts value. + /// This property should be use only for number(int, long, double, byte and etc). + /// public double Minimum { get => MinimumValue ?? 0; @@ -29,6 +74,11 @@ public double Minimum } internal double? MaximumValue { get; set; } + + /// + /// Maximum accepts value. + /// This property should be use only for number(int, long, double, byte and etc). + /// public double Maximum { get => MaximumValue ?? 0; @@ -36,6 +86,10 @@ public double Maximum } internal int? MultipleOfValue { get; set; } + /// + /// Multiple of accepts value. + /// This property should be use only for number(int, long, double, byte and etc). + /// public int MultipleOf { get => MultipleOfValue ?? 0; @@ -43,6 +97,11 @@ public int MultipleOf } internal double? ExclusiveMinimumValue { get; set; } + + /// + /// Exclusive minimum (less than and not equal) accepts value. + /// This property should be use only for number(int, long, double, byte and etc). + /// public double ExclusiveMinimum { get => ExclusiveMinimumValue ?? 0; @@ -50,26 +109,42 @@ public double ExclusiveMinimum } internal double? ExclusiveMaximumValue { get; set; } + /// + /// Exclusive maximum (great than and not equal) accepts value. + /// This property should be use only for number(int, long, double, byte and etc). + /// public double ExclusiveMaximum { get => ExclusiveMaximumValue ?? 0; set => ExclusiveMaximumValue = value; } - internal uint? MinimumLengthValue { get; set; } - public uint MinimumLength + internal int? MinimumLengthValue { get; set; } + /// + /// Minimum string length accepts. + /// This property should be use only for string. + /// + public int MinimumLength { get => MinimumLengthValue.GetValueOrDefault(); set => MinimumLengthValue = value; } - internal uint? MaximumLengthValue { get; set; } - public uint MaximumLength + internal int? MaximumLengthValue { get; set; } + /// + /// Maximum string length accepts. + /// This property should be use only for string. + /// + public int MaximumLength { get => MaximumLengthValue.GetValueOrDefault(); set => MaximumLengthValue = value; } + /// + /// Pattern this action parameter must have. + /// This property should be use only for string. + /// public string? Pattern { get; set; } } } diff --git a/src/Mozilla.IoT.WebThing/Builders/IActionBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/IActionBuilder.cs new file mode 100644 index 0000000..3bc16fb --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/IActionBuilder.cs @@ -0,0 +1,58 @@ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Properties; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + /// Create property. + /// + public interface IActionBuilder + { + /// + /// Set . + /// + /// The to be set. + /// + IActionBuilder SetThing(Thing thing); + + /// + /// Set type. + /// + /// The typeto be set. + /// + IActionBuilder SetThingType(Type thingType); + + /// + /// Set + /// + /// The to be set. + /// + IActionBuilder SetThingOption(ThingOption option); + + /// + /// Add property. + /// + /// The action. + /// The about action. + void Add(MethodInfo action, ThingActionAttribute? information); + + /// + /// Add property. + /// + /// The parameter. + /// The about parameter. + void Add(ParameterInfo parameter, Information information); + + /// + /// Build the + /// + /// New of the + Dictionary Build(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/IEventBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/IEventBuilder.cs new file mode 100644 index 0000000..f0d687f --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/IEventBuilder.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Events; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + /// Create event bind. + /// + public interface IEventBuilder + { + /// + /// Set . + /// + /// The to be set. + /// + IEventBuilder SetThing(Thing thing); + + /// + /// Set type. + /// + /// The typeto be set. + /// + IEventBuilder SetThingType(Type thingType); + + /// + /// Set + /// + /// The to be set. + /// + IEventBuilder SetThingOption(ThingOption option); + + /// + /// Add event. + /// + /// The event. + /// Extra information about event + void Add(EventInfo @event, ThingEventAttribute? eventInfo); + + /// + /// Build the + /// + /// New of the + Dictionary Build(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/IPropertyBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/IPropertyBuilder.cs new file mode 100644 index 0000000..ca699c7 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/IPropertyBuilder.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Reflection; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Properties; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + /// Create property. + /// + public interface IPropertyBuilder + { + /// + /// Set . + /// + /// The to be set. + /// + IPropertyBuilder SetThing(Thing thing); + + /// + /// Set + /// + /// The to be set. + /// + IPropertyBuilder SetThingOption(ThingOption option); + + /// + /// Add property. + /// + /// The property. + /// The about property + void Add(PropertyInfo property, Information information); + + /// + /// Build the + /// + /// New of the + Dictionary Build(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/IThingResponseBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/IThingResponseBuilder.cs new file mode 100644 index 0000000..8f1bf06 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/IThingResponseBuilder.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Reflection; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + /// Create . + /// + public interface IThingResponseBuilder + { + /// + /// Set . + /// + /// The to be set. + /// + IThingResponseBuilder SetThing(Thing thing); + + /// + /// Set + /// + /// The to be set. + /// + IThingResponseBuilder SetThingOption(ThingOption option); + + /// + /// Add event. + /// + /// The event. + /// Extra information about event + void Add(EventInfo @event, ThingEventAttribute? eventInfo); + + /// + /// Add property. + /// + /// The property. + /// + /// The about property + void Add(PropertyInfo property, ThingPropertyAttribute? attribute, Information information); + + /// + /// Add action. + /// + /// The action. + /// + void Add(MethodInfo action, ThingActionAttribute? attribute); + + /// + /// Add property. + /// + /// The parameter. + /// + /// The about parameter + void Add(ParameterInfo parameter, ThingParameterAttribute? attribute, Information information); + + /// + /// Build the . + /// + /// New . + Dictionary Build(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Imp/ActionBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/Imp/ActionBuilder.cs new file mode 100644 index 0000000..d0eac8e --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Imp/ActionBuilder.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + public class ActionBuilder : IActionBuilder + { + private const MethodAttributes s_getSetAttributes = + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + private static readonly ConstructorInfo s_valueTask = typeof(ValueTask).GetConstructor(new[] {typeof(Task)})!; + + + private readonly IActionParameterFactory _factory; + private readonly Dictionary _parameters = new Dictionary(); + + private Thing? _thing; + private ThingOption? _option; + private Type? _thingType; + private ModuleBuilder? _module; + private MethodInfo? _action; + private Dictionary? _actions; + private TypeBuilder? _input; + private string? _name; + + /// + /// Initialize a new instance of . + /// + public ActionBuilder(IActionParameterFactory factory) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + public IActionBuilder SetThing(Thing thing) + { + _thing = thing; + return this; + } + + /// + public IActionBuilder SetThingType(Type thingType) + { + _thingType = thingType; + var baseName = $"{thingType.Name}Actions"; + var assemblyName = new AssemblyName($"{baseName}Assembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + _module = assemblyBuilder.DefineDynamicModule($"{baseName}Module"); + + return this; + } + + /// + public IActionBuilder SetThingOption(ThingOption option) + { + _option = option; + _actions = new Dictionary(option.IgnoreCase ? StringComparer.OrdinalIgnoreCase : null); + return this; + } + + /// + public void Add(MethodInfo action, ThingActionAttribute? attribute) + { + if (_thingType == null || _module == null) + { + throw new InvalidOperationException($"ThingType is null, call {nameof(SetThingType)} before add"); + } + + if (_actions == null || _option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + + if (_input != null) + { + _input.CreateType(); + var (actionInfoBuilder, inputProperty) = CreateActionInfo(_action!, _input, _thingType, _name!); + var factory = CreateActionInfoFactory(actionInfoBuilder, _input, inputProperty); + + _actions.Add(_name!, new ActionCollection(new DictionaryInputConvert(_parameters), + (IActionInfoFactory)Activator.CreateInstance(factory)!)); + } + + _parameters.Clear(); + _name = attribute?.Name ?? action.Name; + _action = action; + _input = _module.DefineType($"{action.Name}Input", TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass); + } + + /// + public void Add(ParameterInfo parameter, Information information) + { + if (_input == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(Add)} before add"); + } + + CreateProperty(_input, information.Name, parameter.ParameterType); + _parameters.Add(information.Name, _factory.Create(parameter.ParameterType, information)); + } + + private static System.Reflection.Emit.PropertyBuilder CreateProperty(TypeBuilder builder, string fieldName, Type type) + { + var field = builder.DefineField($"_{fieldName}", type, FieldAttributes.Private); + var parameterName = fieldName.FirstCharToUpper(); + var propertyBuilder = builder.DefineProperty(parameterName, PropertyAttributes.HasDefault, type, null); + + var getProperty = builder.DefineMethod($"get_{parameterName}", s_getSetAttributes, type, Type.EmptyTypes); + + getProperty.GetILGenerator().Return(field); + + // Define the "set" accessor method for CustomerName. + var setProperty = builder.DefineMethod($"set_{parameterName}", s_getSetAttributes, + null, new[] {type}); + + setProperty.GetILGenerator().Set(field); + + propertyBuilder.SetGetMethod(getProperty); + propertyBuilder.SetSetMethod(setProperty); + + return propertyBuilder; + } + + private (TypeBuilder, System.Reflection.Emit.PropertyBuilder) CreateActionInfo(MethodInfo action, TypeBuilder inputType, Type thingType, string actionName) + { + var actionInfo = _module!.DefineType($"{thingType.Name}{action.Name}ActionInfo", + TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass, + typeof(ActionInfo)); + + var input = CreateProperty(actionInfo, "input", inputType); + + var getProperty = actionInfo.DefineMethod(nameof(ActionInfo.GetActionName), + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, + typeof(string), Type.EmptyTypes); + + getProperty.GetILGenerator().Return(actionName); + + CreateInternalExecuteAsync(action, actionInfo, inputType, input, thingType); + actionInfo.CreateType(); + return (actionInfo, input); + + static void CreateInternalExecuteAsync(MethodInfo action, TypeBuilder actionInfo, TypeBuilder input, PropertyInfo inputProperty, Type thingType) + { + var execute = actionInfo.DefineMethod("InternalExecuteAsync", + MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual, + typeof(ValueTask), new [] { typeof(Thing), typeof(IServiceProvider) }); + + var generator = execute.GetILGenerator(); + generator.CastFirstArg(thingType); + + var inputProperties = input.GetProperties(); + var counter = 0; + + foreach (var parameter in action.GetParameters()) + { + if (parameter.GetCustomAttribute() != null) + { + generator.LoadFromService(parameter.ParameterType); + } + else if(parameter.ParameterType == typeof(CancellationToken)) + { + generator.LoadCancellationToken(); + } + else + { + var property = inputProperties[counter++]; + generator.LoadFromInput(inputProperty.GetMethod!, property.GetMethod!); + } + } + + generator.Call(action); + if (action.ReturnType == typeof(ValueTask)) + { + generator.Emit(OpCodes.Ret); + } + else if(action.ReturnType == typeof(Task)) + { + generator.Return(s_valueTask); + } + else + { + var valueTask = generator.DeclareLocal(typeof(ValueTask)); + generator.Return(valueTask); + } + } + } + + private TypeBuilder CreateActionInfoFactory(Type actionInfo, Type inputType, PropertyInfo inputProperty) + { + var actionInfoFactory = _module!.DefineType($"{actionInfo.Name}Factory", + TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass, + null, new []{ typeof(IActionInfoFactory) }); + + var createMethod = actionInfoFactory.DefineMethod(nameof(IActionInfoFactory.CreateActionInfo), + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + CallingConventions.Standard, + typeof(ActionInfo), + new[] {typeof(Dictionary)}); + + var generator = createMethod.GetILGenerator(); + + generator.NewObj(actionInfo.GetConstructors()[0]); + generator.NewObj(inputType.GetConstructors()[0], true); + + foreach (var property in inputType.GetProperties()) + { + generator.SetProperty(property); + } + + generator.Call(inputProperty.SetMethod!); + generator.Emit(OpCodes.Ret); + + actionInfoFactory.CreateType(); + return actionInfoFactory; + } + + /// + public Dictionary Build() + { + if (_actions == null || _option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + if (_thingType == null || _module == null) + { + throw new InvalidOperationException($"ThingType is null, call {nameof(SetThingType)} before add"); + } + + if (_input != null) + { + _input.CreateType(); + var (actionInfoBuilder, inputProperty) = CreateActionInfo(_action!, _input, _thingType, _name!); + var factory = CreateActionInfoFactory(actionInfoBuilder, _input, inputProperty); + + _actions.Add(_name!, new ActionCollection(new DictionaryInputConvert(_parameters), + (IActionInfoFactory)Activator.CreateInstance(factory)!)); + } + + return _actions; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Imp/EventBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/Imp/EventBuilder.cs new file mode 100644 index 0000000..63c2e13 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Imp/EventBuilder.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Events; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + public class EventBuilder : IEventBuilder + { + private readonly Queue _eventToBind; + + private Thing? _thing; + private ThingOption? _option; + private Type? _thingType; + private TypeBuilder? _builder; + private Dictionary? _events; + + private static readonly ConstructorInfo s_createThing = typeof(Event).GetConstructors(BindingFlags.Public | BindingFlags.Instance)[0]; + private static readonly MethodInfo s_getContext = typeof(Thing).GetProperty(nameof(Thing.ThingContext))?.GetMethod!; + private static readonly MethodInfo s_getEvent = typeof(ThingContext).GetProperty(nameof(ThingContext.Events))?.GetMethod!; + private static readonly MethodInfo s_getItem = typeof(Dictionary).GetMethod("get_Item")!; + private static readonly MethodInfo s_addItem = typeof(EventCollection).GetMethod(nameof(EventCollection.Enqueue))!; + + + /// + /// Initialize a new instance of . + /// + public EventBuilder() + { + _eventToBind = new Queue(); + } + + /// + public IEventBuilder SetThing(Thing thing) + { + _thing = thing; + return this; + } + + /// + public IEventBuilder SetThingType(Type thingType) + { + _thingType = thingType; + var baseName = $"{thingType.Name}EventBinder"; + var assemblyName = new AssemblyName($"{baseName}Assembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule($"{baseName}Module"); + + _builder = moduleBuilder.DefineType(baseName, + TypeAttributes.AutoClass | TypeAttributes.Class | TypeAttributes.Public, + null, null); + + return this; + } + + /// + public IEventBuilder SetThingOption(ThingOption option) + { + _option = option; + _events = new Dictionary(option.IgnoreCase ? StringComparer.OrdinalIgnoreCase : null); + return this; + } + + /// + public void Add(EventInfo @event, ThingEventAttribute? eventInfo) + { + if (_thingType == null || _builder == null) + { + throw new InvalidOperationException($"ThingType is null, call {nameof(SetThingType)} before add"); + } + + if (_events == null || _option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + _eventToBind.Enqueue(@event); + var name = eventInfo?.Name ?? @event.Name; + _events.Add(_option.PropertyNamingPolicy.ConvertName(name), new EventCollection(_option.MaxEventSize)); + var type = @event.EventHandlerType?.GetGenericArguments()[0]!; + var methodBuilder = _builder.DefineMethod($"{@event.Name}Handler", + MethodAttributes.Public | MethodAttributes.Static); + + methodBuilder.SetParameters(typeof(object), type); + + var il = methodBuilder.GetILGenerator(); + + // static void Handler(object sender, @event) + // { + // ((Thing)sender).ThingContext.Events[""].Enqueue(new Event(@event), ""); + // } + // + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, _thingType); + il.EmitCall(OpCodes.Callvirt, s_getContext, null); + il.EmitCall(OpCodes.Callvirt, s_getEvent, null); + il.Emit(OpCodes.Ldstr, name); + il.EmitCall(OpCodes.Callvirt, s_getItem, null); + il.Emit(OpCodes.Ldarg_1); + + if (type.IsValueType) + { + il.Emit(OpCodes.Box, type); + } + + il.Emit(OpCodes.Newobj, s_createThing); + il.Emit(OpCodes.Ldstr, _option.PropertyNamingPolicy.ConvertName(name)); + il.EmitCall(OpCodes.Callvirt, s_addItem, null); + il.Emit(OpCodes.Ret); + } + + /// + public Dictionary Build() + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)} before build"); + } + + if (_builder == null) + { + throw new InvalidOperationException($"ThingType is null, call {nameof(SetThingType)} before build"); + } + + var type = _builder.CreateType()!; + while (_eventToBind.TryDequeue(out var @event)) + { + var @delegate = Delegate.CreateDelegate(@event.EventHandlerType!, type, $"{@event.Name}Handler"); + @event.AddEventHandler(_thing, @delegate ); + } + + return _events!; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Imp/JsonType.cs b/src/Mozilla.IoT.WebThing/Builders/Imp/JsonType.cs new file mode 100644 index 0000000..f00d234 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Imp/JsonType.cs @@ -0,0 +1,11 @@ +namespace Mozilla.IoT.WebThing.Builders +{ + public enum JsonType + { + Boolean, + String, + Integer, + Number, + Array + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Imp/PropertyBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/Imp/PropertyBuilder.cs new file mode 100644 index 0000000..d8a64d3 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Imp/PropertyBuilder.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; +using Mozilla.IoT.WebThing.Properties; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + public class PropertyBuilder : IPropertyBuilder + { + private readonly IPropertyFactory _factory; + private Thing? _thing; + private ThingOption? _option; + private Dictionary? _properties; + + /// + /// Initialize a new instance of . + /// + /// The . + public PropertyBuilder(IPropertyFactory factory) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + public IPropertyBuilder SetThing(Thing thing) + { + _thing = thing; + return this; + } + + + /// + public IPropertyBuilder SetThingOption(ThingOption option) + { + _option = option; + _properties = new Dictionary(option.IgnoreCase ? StringComparer.OrdinalIgnoreCase : null); + return this; + } + + /// + public void Add(PropertyInfo property, Information information) + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)} before build"); + } + + if (_properties == null || _option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + var getter = GetGetMethod(property); + + if (information.IsReadOnly) + { + _properties.Add(_option.PropertyNamingPolicy.ConvertName(information.Name), new PropertyReadOnly(_thing, getter)); + return; + } + + var setter = GetSetMethod(property); + + _properties.Add(_option.PropertyNamingPolicy.ConvertName(information.Name), + _factory.Create(property.PropertyType, information, _thing, setter, getter)); + + static Func GetGetMethod(PropertyInfo property) + { + var instance = Expression.Parameter(typeof(object), "instance"); + var instanceCast = property.DeclaringType!.IsValueType ? + Expression.Convert(instance, property.DeclaringType) : Expression.TypeAs(instance, property.DeclaringType); + + var call = Expression.Call(instanceCast, property.GetGetMethod()); + var typeAs = Expression.TypeAs(call, typeof(object)); + + return Expression.Lambda>(typeAs, instance).Compile(); + } + + static Action GetSetMethod(PropertyInfo property) + { + var instance = Expression.Parameter(typeof(object), "instance"); + var value = Expression.Parameter(typeof(object), "value"); + + // value as T is slightly faster than (T)value, so if it's not a value type, use that + var instanceCast = property.DeclaringType!.IsValueType ? + Expression.Convert(instance, property.DeclaringType) : Expression.TypeAs(instance, property.DeclaringType); + + var valueCast = property.PropertyType.IsValueType ? + Expression.Convert(value, property.PropertyType) : Expression.TypeAs(value, property.PropertyType); + + var call = Expression.Call(instanceCast, property.GetSetMethod(), valueCast); + return Expression.Lambda>(call, new[] {instance, value}).Compile()!; + } + } + + /// + public Dictionary Build() + { + if (_properties == null || _option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + return _properties!; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Imp/ThingResponseBuilder.cs b/src/Mozilla.IoT.WebThing/Builders/Imp/ThingResponseBuilder.cs new file mode 100644 index 0000000..8d6b099 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Imp/ThingResponseBuilder.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + public class ThingResponseBuilder : IThingResponseBuilder + { + private Thing? _thing; + private ThingOption? _option; + private readonly Dictionary _events = new Dictionary(); + private readonly Dictionary _properties = new Dictionary(); + private readonly Dictionary _actions = new Dictionary(); + + private Dictionary? _parameters; + private string _thingName = string.Empty; + + /// + public IThingResponseBuilder SetThing(Thing thing) + { + _thing = thing; + + if (_option != null) + { + _thingName = _option.PropertyNamingPolicy.ConvertName(_thing.Name); + } + return this; + } + + /// + public IThingResponseBuilder SetThingOption(ThingOption option) + { + _option = option; + + if (_thing != null) + { + _thingName = _option.PropertyNamingPolicy.ConvertName(_thing.Name); + } + + return this; + } + + /// + public void Add(EventInfo @event, ThingEventAttribute? eventInfo) + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)} before build"); + } + + if (_option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + var information = new Dictionary(); + + if (eventInfo != null) + { + if (!_option.IgnoreNullValues || eventInfo.Title != null) + { + information.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Title)), eventInfo.Title); + } + + if (!_option.IgnoreNullValues || eventInfo.Description != null) + { + information.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Description)), eventInfo.Description); + } + + if (!_option.IgnoreNullValues || eventInfo.Unit != null) + { + information.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Unit)), eventInfo.Unit); + } + + AddTypeProperty(information, eventInfo.Type); + } + + var eventName = _option.PropertyNamingPolicy.ConvertName(eventInfo?.Name ?? @event.Name); + + information.Add(_option.PropertyNamingPolicy.ConvertName("Link"), new[] + { + new Link($"/things/{_thingName}/events/{eventName}", "event") + }); + + _events.Add(eventName, information); + } + + /// + public void Add(PropertyInfo property, ThingPropertyAttribute? attribute, Information information) + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)} before build"); + } + + if (_option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + var propertyInformation = new Dictionary(); + + if (!_option.IgnoreNullValues || attribute?.Title != null) + { + propertyInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Title)), attribute?.Title); + } + + if (!_option.IgnoreNullValues || attribute?.Description != null) + { + propertyInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Description)), attribute?.Description); + } + + if (!_option.IgnoreNullValues || attribute?.Unit != null) + { + propertyInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Unit)), attribute?.Unit); + } + + AddTypeProperty(propertyInformation, attribute?.Type); + + AddInformation(propertyInformation, information, ToJsonType(property.PropertyType), true); + var propertyName = _option.PropertyNamingPolicy.ConvertName(attribute?.Name ?? property.Name); + + propertyInformation.Add(_option.PropertyNamingPolicy.ConvertName("Link"), new[] + { + new Link($"/things/{_thingName}/properties/{propertyName}", "property") + }); + + _properties.Add(propertyName, propertyInformation); + } + + /// + public void Add(MethodInfo action, ThingActionAttribute? attribute) + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)}"); + } + + if (_option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)}"); + } + + var propertyName = _option.PropertyNamingPolicy.ConvertName(attribute?.Name ?? action.Name); + + var actionInformation = new Dictionary(); + + if (!_option.IgnoreNullValues || attribute?.Title != null) + { + actionInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Title)), attribute?.Title); + } + + if (!_option.IgnoreNullValues || attribute?.Description != null) + { + actionInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Description)), attribute?.Description); + } + + actionInformation.Add(_option.PropertyNamingPolicy.ConvertName("Link"), new[] + { + new Link($"/things/{_thingName}/actions/{propertyName}", "action") + }); + + var input = new Dictionary(); + + AddTypeProperty(input, attribute?.Type); + + input.Add("type", "object"); + + _parameters = new Dictionary(); + + input.Add(_option.PropertyNamingPolicy.ConvertName("Properties"), _parameters); + + actionInformation.Add(_option.PropertyNamingPolicy.ConvertName("Input"), input); + + _actions.Add(propertyName, actionInformation); + } + + /// + public void Add(ParameterInfo parameter, ThingParameterAttribute? attribute, Information information) + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)}"); + } + + if (_option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)}"); + } + + if (_parameters == null) + { + throw new InvalidOperationException($"Parameter is null, call {nameof(Add)}"); + } + + var parameterInformation = new Dictionary(); + + if (!_option.IgnoreNullValues || attribute?.Title != null) + { + parameterInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Title)), attribute?.Title); + } + + if (!_option.IgnoreNullValues || attribute?.Description != null) + { + parameterInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Description)), attribute?.Description); + } + + if (!_option.IgnoreNullValues || attribute?.Unit != null) + { + parameterInformation.Add(_option.PropertyNamingPolicy.ConvertName(nameof(ThingEventAttribute.Unit)), attribute?.Unit); + } + + AddInformation(parameterInformation, information, ToJsonType(parameter.ParameterType), false); + var parameterName = _option.PropertyNamingPolicy.ConvertName(attribute?.Name ?? parameter.Name); + + _parameters.Add(parameterName, parameterInformation); + } + + private void AddInformation(Dictionary builder, Information information, JsonType jsonType, bool writeIsReadOnlu) + { + builder.Add("type", jsonType.ToString().ToLower()); + + if (_option == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThingOption)} before build"); + } + + if (writeIsReadOnlu) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.IsReadOnly)), information.IsReadOnly); + } + + switch(jsonType) + { + case JsonType.String: + if (!_option.IgnoreNullValues || information.MinimumLength.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.MinimumLength)), information.MinimumLength); + } + + if (!_option.IgnoreNullValues || information.MaximumLength.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.MaximumLength)), information.MaximumLength); + } + + if (!_option.IgnoreNullValues || information.Pattern != null) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.Pattern)), information.Pattern); + } + + if (!_option.IgnoreNullValues || information.Enums != null) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.Enums)), information.Enums); + } + break; + case JsonType.Integer: + case JsonType.Number: + if (!_option.IgnoreNullValues || information.Minimum.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.Minimum)), information.Minimum); + } + + if (!_option.IgnoreNullValues || information.Maximum.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.Maximum)), information.Maximum); + } + + if (!_option.IgnoreNullValues || information.ExclusiveMinimum.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.ExclusiveMinimum)), information.ExclusiveMinimum); + } + + if (!_option.IgnoreNullValues || information.ExclusiveMaximum.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.ExclusiveMaximum)), information.ExclusiveMaximum); + } + + if (!_option.IgnoreNullValues || information.MultipleOf.HasValue) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.MultipleOf)), information.MultipleOf); + } + + if (!_option.IgnoreNullValues || information.Enums != null) + { + builder.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Information.Enums)), information.Enums); + } + break; + case JsonType.Array: + case JsonType.Boolean: + break; + default: + throw new ArgumentOutOfRangeException(nameof(jsonType), jsonType, null); + } + } + + private static JsonType ToJsonType(Type type) + { + type = type.GetUnderlyingType(); + + if (type == typeof(string) + || type == typeof(char) + || type == typeof(DateTime) + || type == typeof(DateTimeOffset) + || type == typeof(Guid) + || type == typeof(TimeSpan) + || type.IsEnum) + { + return JsonType.String; + } + + if (type == typeof(bool)) + { + return JsonType.Boolean; + } + + if (type == typeof(int) + || type == typeof(sbyte) + || type == typeof(byte) + || type == typeof(short) + || type == typeof(long) + || type == typeof(uint) + || type == typeof(ulong) + || type == typeof(ushort)) + { + return JsonType.Integer; + } + + if (type == typeof(double) + || type == typeof(float) + || type == typeof(decimal)) + { + return JsonType.Number; + } + + return JsonType.Array; + } + + private void AddTypeProperty(Dictionary builder, string[]? types) + { + if (_option == null) + { + throw new InvalidOperationException($"ThingOption is null, call {nameof(SetThingOption)} before add"); + } + + if (_option.IgnoreNullValues && types == null) + { + return; + } + + if (types == null ) + { + builder.Add("@type", null); + return; + } + + if (types.Length == 1) + { + builder.Add("@type", types[0]); + return; + } + + builder.Add("@type", types); + } + + /// + public Dictionary Build() + { + if (_thing == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThing)} before build"); + } + + if (_option == null) + { + throw new InvalidOperationException($"Thing is null, call {nameof(SetThingOption)} before build"); + } + + var result = new Dictionary + { + ["@context"] = _thing.Context + }; + + if (!_option.IgnoreNullValues || _thing.Title != null) + { + result.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Thing.Title)), _thing.Title); + } + + if (!_option.IgnoreNullValues || _thing.Description != null) + { + result.Add(_option.PropertyNamingPolicy.ConvertName(nameof(Thing.Description)), _thing.Description); + } + + AddTypeProperty(result, _thing.Type); + + if (_events.Any()) + { + result.Add(_option.PropertyNamingPolicy.ConvertName("Events"), _events); + } + + if (_properties.Any()) + { + result.Add(_option.PropertyNamingPolicy.ConvertName("Properties"), _properties); + } + + if (_actions.Any()) + { + result.Add(_option.PropertyNamingPolicy.ConvertName("Actions"), _actions); + } + + + var links = new List(4) + { + new Link("properties", $"/things/{_thingName}/properties"), + new Link("events", $"/things/{_thingName}/events"), + new Link("actions", $"/things/{_thingName}/actions") + }; + + result.Add(_option.PropertyNamingPolicy.ConvertName("Links"), links); + return result; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Builders/Information.cs b/src/Mozilla.IoT.WebThing/Builders/Information.cs new file mode 100644 index 0000000..3508397 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Builders/Information.cs @@ -0,0 +1,109 @@ +using System.Linq; + +namespace Mozilla.IoT.WebThing.Builders +{ + /// + /// Represent property/parameter validation + /// + public readonly struct Information + { + private readonly bool _isNullable; + + /// + /// Initialize a new instance of . + /// + /// The minimum value. + /// The maximum value. + /// The exclusive minimum value. + /// The exclusive maximum value. + /// The multiple of value. + /// The minimum length value. + /// The maximum length value. + /// The pattern value. + /// The enums values. + /// Is is read-only + /// The name + /// + public Information(double? minimum, double? maximum, + double? exclusiveMinimum, double? exclusiveMaximum, double? multipleOf, + int? minimumLength, int? maximumLength, string? pattern, object[]? enums, + bool isReadOnly, string name, bool isNullable) + { + Minimum = minimum; + Maximum = maximum; + ExclusiveMinimum = exclusiveMinimum; + ExclusiveMaximum = exclusiveMaximum; + MultipleOf = multipleOf; + MinimumLength = minimumLength; + MaximumLength = maximumLength; + Pattern = pattern; + Enums = enums; + IsReadOnly = isReadOnly; + Name = name; + _isNullable = isNullable; + } + + + /// + /// The name. + /// + public string Name { get; } + + /// + /// Minimum value. + /// + public double? Minimum { get; } + + /// + /// Maximum value. + /// + public double? Maximum { get; } + + /// + /// Exclusive minimum value. + /// + public double? ExclusiveMinimum { get; } + + /// + /// Exclusive maximum value. + /// + public double? ExclusiveMaximum { get; } + + /// + /// Multiple of value. + /// + public double? MultipleOf { get; } + + /// + /// Minimum length value. + /// + public int? MinimumLength { get; } + + /// + /// Maximum length value. + /// + public int? MaximumLength { get; } + + /// + /// String pattern value. + /// + public string? Pattern { get; } + + /// + /// Possible values. + /// + public object[]? Enums { get; } + + /// + /// If is Read-only + /// + public bool IsReadOnly { get; } + + + /// + /// IsNullable. + /// + public bool IsNullable + => _isNullable || (Enums != null && Enums.Contains(null!)); + } +} diff --git a/src/Mozilla.IoT.WebThing/Const.cs b/src/Mozilla.IoT.WebThing/Const.cs index 3d3ddf0..53fcb76 100644 --- a/src/Mozilla.IoT.WebThing/Const.cs +++ b/src/Mozilla.IoT.WebThing/Const.cs @@ -1,8 +1,18 @@ namespace Mozilla.IoT.WebThing { + /// + /// The default values + /// public class Const { + /// + /// Default value of thing context. + /// public const string DefaultContext = "https://iot.mozilla.org/schemas"; + + /// + /// Default value of content type. + /// public const string ContentType = "application/json"; } } diff --git a/src/Mozilla.IoT.WebThing/Context.cs b/src/Mozilla.IoT.WebThing/Context.cs deleted file mode 100644 index a92e716..0000000 --- a/src/Mozilla.IoT.WebThing/Context.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net.WebSockets; -using Mozilla.IoT.WebThing.Converts; - -namespace Mozilla.IoT.WebThing -{ - public class Context - { - public Context(IThingConverter converter, - IProperties properties, - Dictionary events, - Dictionary actions) - { - Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - Properties = properties ?? throw new ArgumentNullException(nameof(properties)); - Events = events ?? throw new ArgumentNullException(nameof(events)); - Actions = actions ?? throw new ArgumentNullException(nameof(actions)); - } - - public IThingConverter Converter { get; } - - public IProperties Properties { get; } - public Dictionary Events { get; } - public Dictionary Actions { get; } - public ConcurrentDictionary Sockets { get; } = new ConcurrentDictionary(); - } -} diff --git a/src/Mozilla.IoT.WebThing/Converts/ActionStatusConverter.cs b/src/Mozilla.IoT.WebThing/Converts/ActionStatusConverter.cs new file mode 100644 index 0000000..b03d8d4 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Converts/ActionStatusConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Mozilla.IoT.WebThing.Actions; + +namespace Mozilla.IoT.WebThing.Converts +{ + /// + /// Convert + /// + public class ActionStatusConverter : JsonConverter + { + private static readonly Type s_status = typeof(ActionStatus); + + /// + public override bool CanConvert(Type typeToConvert) + => s_status == typeToConvert; + + /// + public override ActionStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Enum.Parse(reader.GetString(), true); + + /// + public override void Write(Utf8JsonWriter writer, ActionStatus value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString().ToLower()); + } +} diff --git a/src/Mozilla.IoT.WebThing/Converts/IThingConverter.cs b/src/Mozilla.IoT.WebThing/Converts/IThingConverter.cs deleted file mode 100644 index 12f7b6d..0000000 --- a/src/Mozilla.IoT.WebThing/Converts/IThingConverter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Converts -{ - public interface IThingConverter - { - void Write(Utf8JsonWriter writer, Thing value, JsonSerializerOptions options); - } -} diff --git a/src/Mozilla.IoT.WebThing/Converts/ThingConverter.cs b/src/Mozilla.IoT.WebThing/Converts/ThingConverter.cs deleted file mode 100644 index 8c567f3..0000000 --- a/src/Mozilla.IoT.WebThing/Converts/ThingConverter.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using Mozilla.IoT.WebThing.Extensions; - -namespace Mozilla.IoT.WebThing.Converts -{ - public class ThingConverter : JsonConverter - { - - private readonly ThingOption _option; - public ThingConverter(ThingOption option) - { - _option = option; - } - - public override bool CanConvert(Type typeToConvert) - { - return typeToConvert == typeof(Thing) || typeToConvert.IsSubclassOf(typeof(Thing)); - } - - public override Thing Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - - public override void Write(Utf8JsonWriter writer, Thing value, JsonSerializerOptions options) - { - if (writer == null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - writer.WriteStartObject(); - writer.WriteString("@context", value.Context); - var builder = new UriBuilder(value.Prefix) {Path = $"/things/{options.GetPropertyName(value.Name)}"}; - if (_option.UseThingAdapterUrl) - { - WriteProperty(writer, "Id", options.GetPropertyName(value.Name), options); - WriteProperty(writer, "href", builder.Path, options); - WriteProperty(writer, "base", builder.Uri.ToString(), options); - } - else - { - WriteProperty(writer, "Id", builder.Uri.ToString(), options); - } - - value.ThingContext.Converter.Write(writer, value, options); - - StartArray(writer, "Links", options); - - writer.WriteStartObject(); - WriteProperty(writer, "rel", "properties", options); - WriteProperty(writer, "href", $"/things/{options.GetPropertyName(value.Name)}/properties", options); - writer.WriteEndObject(); - - writer.WriteStartObject(); - WriteProperty(writer, "rel", "actions", options); - WriteProperty(writer, "href", $"/things/{options.GetPropertyName(value.Name)}/actions", options); - writer.WriteEndObject(); - - writer.WriteStartObject(); - WriteProperty(writer, "rel", "events", options); - WriteProperty(writer, "href", $"/things/{options.GetPropertyName(value.Name)}/events", options); - writer.WriteEndObject(); - - builder.Scheme = value.Prefix.Scheme == "http" ? "ws" : "wss"; - writer.WriteStartObject(); - WriteProperty(writer, "rel", "alternate", options); - WriteProperty(writer, "href", builder.Uri.ToString(), options); - writer.WriteEndObject(); - - writer.WriteEndArray(); - - writer.WriteEndObject(); - } - - #region Writer - - private static string GetName(string name, JsonNamingPolicy policy) - => policy != null ? policy.ConvertName(name) : name; - - private static void WriteProperty(Utf8JsonWriter writer, string name, string? value, JsonSerializerOptions options) - { - var propertyName = GetName(name, options.PropertyNamingPolicy); - if (value == null) - { - if (!options.IgnoreNullValues) - { - writer.WriteNull(propertyName); - } - } - else - { - writer.WriteString(propertyName, value); - } - } - - private static void StartArray(Utf8JsonWriter writer, string propertyName, JsonSerializerOptions options) - { - var name = GetName(propertyName, options.PropertyNamingPolicy); - writer.WriteStartArray(name); - } - - #endregion - } -} diff --git a/src/Mozilla.IoT.WebThing/Endpoints/DeleteAction.cs b/src/Mozilla.IoT.WebThing/Endpoints/DeleteAction.cs index f51fee2..a1f638c 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/DeleteAction.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/DeleteAction.cs @@ -2,13 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; -using Mozilla.IoT.WebThing.Extensions; namespace Mozilla.IoT.WebThing.Endpoints { @@ -31,8 +28,6 @@ public static Task InvokeAsync(HttpContext context) return Task.CompletedTask; } - var option = service.GetRequiredService(); - var actionName = context.GetRouteData("action"); var id = Guid.Parse(context.GetRouteData("id")); @@ -43,7 +38,7 @@ public static Task InvokeAsync(HttpContext context) return Task.CompletedTask; } - if (!actionContext.Actions.TryRemove(id, out var actionInfo)) + if (!actionContext.TryRemove(id, out var actionInfo)) { logger.LogInformation("{actionName} Action with {id} id not found in {thingName}", actionName, id, thingName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetAction.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetAction.cs index ccc884b..99bfd93 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetAction.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetAction.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; namespace Mozilla.IoT.WebThing.Endpoints { @@ -36,13 +35,13 @@ public static async Task InvokeAsync(HttpContext context) if (!thing.ThingContext.Actions.TryGetValue(actionName, out var actionContext)) { - logger.LogInformation("Action not found. [Thing: {name}][Action: {action}]", thingName, actionName); + logger.LogInformation("{action} action not found. [Thing: {name}]", actionName, thingName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - logger.LogInformation("Action found. [Thing: {name}][Action: {action}]", thingName, actionName); - await context.WriteBodyAsync(HttpStatusCode.OK, actionContext.Actions, option) + logger.LogInformation("{action} action found. [Thing: {name}]", actionName, thingName); + await context.WriteBodyAsync(HttpStatusCode.OK, actionContext, option) .ConfigureAwait(false); } } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetActionById.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetActionById.cs index 1bc6969..72467ce 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetActionById.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetActionById.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; namespace Mozilla.IoT.WebThing.Endpoints { @@ -36,19 +35,19 @@ public static async Task InvokeAsync(HttpContext context) if (!thing.ThingContext.Actions.TryGetValue(actionName, out var actionContext)) { - logger.LogInformation("Action not found. [Thing: {name}][Action: {action}]", thingName, actionName); + logger.LogInformation("{action} action not found. [Thing: {name}]", actionName, thingName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - if (!actionContext.Actions.TryGetValue(id, out var actionInfo)) + if (!actionContext.TryGetValue(id, out var actionInfo)) { - logger.LogInformation("Action id not found. [Thing: {name}][Action: {action}][Id: {id}]", thingName, actionName, id); + logger.LogInformation("{action} action {id} id not found. [Thing: {name}]", actionName, thingName, id); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - logger.LogInformation("Action Id found. [Thing: {name}][Action: {action}][Id: {id}]", thingName, actionName, id); + logger.LogInformation("{action} action with {id} Id found. [Thing: {name}]", actionName, id, thingName); await context.WriteBodyAsync(HttpStatusCode.OK, actionInfo, option) .ConfigureAwait(false); } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetActions.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetActions.cs index 2cbf948..e39ba8c 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetActions.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetActions.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; namespace Mozilla.IoT.WebThing.Endpoints { @@ -36,7 +35,7 @@ public static async Task InvokeAsync(HttpContext context) foreach (var actions in thing.ThingContext.Actions) { - foreach (var value in actions.Value.Actions) + foreach (var value in actions.Value) { result.AddLast(new Dictionary {[actions.Key] = value}); } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetAllThings.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetAllThings.cs index 7391bd3..6437002 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetAllThings.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetAllThings.cs @@ -1,14 +1,11 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; using Mozilla.IoT.WebThing.Extensions; namespace Mozilla.IoT.WebThing.Endpoints @@ -20,23 +17,13 @@ internal static Task InvokeAsync(HttpContext context) var service = context.RequestServices; var logger = service.GetRequiredService>(); var things = service.GetRequiredService>(); - - logger.LogDebug("Verify if Things have prefix"); - foreach (var thing in things) - { - if (thing.Prefix == null) - { - logger.LogDebug("Thing without prefix. [Name: {name}]", thing.Name); - thing.Prefix = new Uri(UriHelper.BuildAbsolute(context.Request.Scheme, - context.Request.Host)); - } - } logger.LogInformation("Found {counter} things", things.Count()); context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = Const.ContentType; - - return JsonSerializer.SerializeAsync(context.Response.Body, things, + + return JsonSerializer.SerializeAsync(context.Response.Body, + things.Select(thing => thing.ThingContext.Response).ToList(), service.GetRequiredService().ToJsonSerializerOptions(), context.RequestAborted); } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetProperties.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetProperties.cs index 8667c08..7b93d24 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetProperties.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetProperties.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing.Extensions; namespace Mozilla.IoT.WebThing.Endpoints { @@ -29,14 +30,22 @@ public static Task InvokeAsync(HttpContext context) context.Response.StatusCode = (int)HttpStatusCode.NotFound; return Task.CompletedTask; } - - var properties = thing.ThingContext.Properties.GetProperties()!; - logger.LogInformation("Found Thing with {counter} properties. [Thing: {name}]", properties.Count, thing.Name); + + logger.LogInformation("Found Thing with {counter} properties. [Thing: {name}]", thing.ThingContext.Properties.Count, thing.Name); + + var properties = new Dictionary(); + + foreach (var (propertyName, property) in thing.ThingContext.Properties) + { + properties.Add(propertyName, property.GetValue()); + } context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = Const.ContentType; - return JsonSerializer.SerializeAsync(context.Response.Body, properties, service.GetRequiredService()); + return JsonSerializer.SerializeAsync(context.Response.Body, properties, + service.GetRequiredService() + .ToJsonSerializerOptions()); } } } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetProperty.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetProperty.cs index b43ab6d..7400d87 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetProperty.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetProperty.cs @@ -30,22 +30,25 @@ public static Task InvokeAsync(HttpContext context) return Task.CompletedTask; } - var property = context.GetRouteData("property"); - var properties = thing.ThingContext.Properties.GetProperties(property); + var propertyName = context.GetRouteData("property"); - if (properties == null) + if (!thing.ThingContext.Properties.TryGetValue(propertyName, out var property)) { - logger.LogInformation("Property not found. [Thing: {thingName}][Property: {propertyName}]", thing.Name, property); + logger.LogInformation("Property not found. [Thing: {thingName}][Property: {propertyName}]", thing.Name, propertyName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return Task.CompletedTask; } - - logger.LogInformation("Found Property. [Thing: {thingName}][Property: {propertyName}]", thing.Name, property); + + logger.LogInformation("Found Property. [Thing: {thingName}][Property: {propertyName}]", thing.Name, propertyName); context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = Const.ContentType; - return JsonSerializer.SerializeAsync(context.Response.Body, properties, service.GetRequiredService()); + return JsonSerializer.SerializeAsync(context.Response.Body, new Dictionary + { + [propertyName] = property.GetValue() + }, + service.GetRequiredService()); } } } diff --git a/src/Mozilla.IoT.WebThing/Endpoints/GetThing.cs b/src/Mozilla.IoT.WebThing/Endpoints/GetThing.cs index d070665..39dc8cf 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/GetThing.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/GetThing.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Converts; using Mozilla.IoT.WebThing.Extensions; namespace Mozilla.IoT.WebThing.Endpoints @@ -33,13 +32,6 @@ internal static Task InvokeAsync(HttpContext context) return Task.CompletedTask; } - if (thing.Prefix == null) - { - logger.LogDebug("Thing without prefix. [Thing: {name}]", thing.Name); - thing.Prefix = new Uri(UriHelper.BuildAbsolute(context.Request.Scheme, - context.Request.Host)); - } - logger.LogInformation("Found 1 Thing. [Thing: {name}]", thing.Name); context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.ContentType = Const.ContentType; diff --git a/src/Mozilla.IoT.WebThing/Endpoints/PostAction.cs b/src/Mozilla.IoT.WebThing/Endpoints/PostAction.cs index 966b79e..4df1ce1 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/PostAction.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/PostAction.cs @@ -32,35 +32,31 @@ public static async Task InvokeAsync(HttpContext context) var jsonOption = service.GetRequiredService(); var option = service.GetRequiredService(); - - var actions = await context.FromBodyAsync>(jsonOption) - .ConfigureAwait(false); - + var actionName = context.GetRouteData("action"); - if (!thing.ThingContext.Actions.TryGetValue(actionName, out var actionContext)) + if (!thing.ThingContext.Actions.TryGetValue(actionName, out var actions)) { - logger.LogInformation("{actionName} Action not found in {thingName}", actions, thingName); + logger.LogInformation("{actionName} Action not found in {thingName}", actionName, thingName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - - if (actions.Keys.Any(x => x != actionName)) - { - logger.LogInformation("Payload has invalid action. [Name: {thingName}][Action Name: {actionName}]", thingName, actionName); - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; - } - + + var jsonActions = await context.FromBodyAsync(jsonOption) + .ConfigureAwait(false); + var actionsToExecute = new LinkedList(); - foreach (var (_, json) in actions) + foreach (var property in jsonActions.EnumerateObject()) { - logger.LogTrace("{actionName} Action found. [Name: {thingName}]", actions, thingName); - var action = (ActionInfo)JsonSerializer.Deserialize(json.GetRawText(), - actionContext.ActionType, jsonOption); - - if (!action.IsValid()) + if (!property.Name.Equals(actionName, StringComparison.InvariantCultureIgnoreCase)) + { + logger.LogInformation("Invalid {actionName} action. [Thing: {thingName}]", actions, thingName); + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + + if (!actions.TryAdd(property.Value, out var action)) { logger.LogInformation("{actionName} Action has invalid parameters. [Name: {thingName}]", actions, thingName); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; @@ -69,23 +65,21 @@ public static async Task InvokeAsync(HttpContext context) action.Thing = thing; var namePolicy = option.PropertyNamingPolicy; - action.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(actionName)}/{action.Id}"; + action.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(actionName)}/{action.GetActionName()}"; actionsToExecute.AddLast(action); } - foreach (var actionInfo in actionsToExecute) + foreach (var action in actionsToExecute) { - logger.LogInformation("Going to execute action {actionName}. [Name: {thingName}]", actionName, thingName); + logger.LogInformation("Going to execute {actionName} action with {id} Id. [Name: {thingName}]", action.GetActionName(), action.GetId(), thingName); - actionInfo.ExecuteAsync(thing, service) + _ = action.ExecuteAsync(thing, service) .ConfigureAwait(false); - - actionContext.Actions.Add(actionInfo.Id, actionInfo); } if (actionsToExecute.Count == 1) { - await context.WriteBodyAsync(HttpStatusCode.Created, actionsToExecute.First.Value, jsonOption) + await context.WriteBodyAsync(HttpStatusCode.Created, actionsToExecute.First!.Value, jsonOption) .ConfigureAwait(false); } else diff --git a/src/Mozilla.IoT.WebThing/Endpoints/PostActions.cs b/src/Mozilla.IoT.WebThing/Endpoints/PostActions.cs index 5afbd4c..81d0dcd 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/PostActions.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/PostActions.cs @@ -30,53 +30,48 @@ public static async Task InvokeAsync(HttpContext context) return; } - context.Request.EnableBuffering(); var jsonOption = service.GetRequiredService(); var option = service.GetRequiredService(); - var actions = await context.FromBodyAsync>(jsonOption) + var jsonAction = await context.FromBodyAsync(jsonOption) .ConfigureAwait(false); var actionsToExecute = new LinkedList(); - foreach (var (actionName, json) in actions) + + foreach (var property in jsonAction.EnumerateObject()) { - if (!thing.ThingContext.Actions.TryGetValue(actionName, out var actionContext)) + if (!thing.ThingContext.Actions.TryGetValue(property.Name, out var actions)) { logger.LogInformation("{actionName} Action not found in {thingName}", actions, thingName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - - logger.LogTrace("{actionName} Action found. [Name: {thingName}]", actions, thingName); - var action = (ActionInfo)JsonSerializer.Deserialize(json.GetRawText(), - actionContext.ActionType, jsonOption); - - if (!action.IsValid()) + + if (!actions.TryAdd(property.Value, out var action)) { logger.LogInformation("{actionName} Action has invalid parameters. [Name: {thingName}]", actions, thingName); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } - - actionsToExecute.AddLast(action); + action.Thing = thing; var namePolicy = option.PropertyNamingPolicy; - action.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(actionName)}/{action.Id}"; + action.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(action.GetActionName())}/{action.GetId()}"; + + actionsToExecute.AddLast(action); + } foreach (var actionInfo in actionsToExecute) { logger.LogInformation("Going to execute {actionName} action. [Name: {thingName}]", actionInfo.GetActionName(), thingName); - - thing.ThingContext.Actions[actionInfo.GetActionName()].Actions.Add(actionInfo.Id, actionInfo); - - actionInfo.ExecuteAsync(thing, service) + _ = actionInfo.ExecuteAsync(thing, service) .ConfigureAwait(false); } if (actionsToExecute.Count == 1) { - await context.WriteBodyAsync(HttpStatusCode.Created, actionsToExecute.First.Value, jsonOption) + await context.WriteBodyAsync(HttpStatusCode.Created, actionsToExecute.First!.Value, jsonOption) .ConfigureAwait(false); } else diff --git a/src/Mozilla.IoT.WebThing/Endpoints/PutProperty.cs b/src/Mozilla.IoT.WebThing/Endpoints/PutProperty.cs index d576b8d..ba24ae3 100644 --- a/src/Mozilla.IoT.WebThing/Endpoints/PutProperty.cs +++ b/src/Mozilla.IoT.WebThing/Endpoints/PutProperty.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing.Properties; namespace Mozilla.IoT.WebThing.Endpoints { @@ -29,39 +30,48 @@ public static async Task InvokeAsync(HttpContext context) return; } - var property = context.GetRouteData("property"); + var propertyName = context.GetRouteData("property"); - logger.LogInformation("Going to set property {propertyName}", property); + logger.LogInformation("Going to set property {propertyName}", propertyName); var jsonOptions = service.GetRequiredService(); - var json = await context.FromBodyAsync(jsonOptions) + var jsonElement = await context.FromBodyAsync(jsonOptions) .ConfigureAwait(false); - - var result = thing.ThingContext.Properties.SetProperty(property, json.GetProperty(property)); - - if (result == SetPropertyResult.NotFound) + + if (!thing.ThingContext.Properties.TryGetValue(propertyName, out var property)) { - logger.LogInformation("Property not found. [Thing Name: {thingName}][Property Name: {propertyName}]", thing.Name, property); + logger.LogInformation("Property not found. [Thing: {thingName}][Property: {propertyName}]", thing.Name, propertyName); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } - - if (result == SetPropertyResult.InvalidValue) + + var jsonProperties = jsonElement.EnumerateObject(); + foreach (var jsonProperty in jsonProperties) { - logger.LogInformation("Property with Invalid. [Thing Name: {thingName}][Property Name: {propertyName}]", thing.Name, property); - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; + if (propertyName.Equals(jsonProperty.Name)) + { + switch (property.SetValue(jsonProperty.Value)) + { + case SetPropertyResult.InvalidValue: + logger.LogInformation("Property with Invalid. [Thing Name: {thingName}][Property Name: {propertyName}]", thing.Name, property); + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + case SetPropertyResult.ReadOnly: + logger.LogInformation("Read-Only Property. [Thing Name: {thingName}][Property Name: {propertyName}]", thing.Name, property); + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + } + else + { + logger.LogInformation("Invalid property. [Thing: {thingName}][Excepted property: {propertyName}][Actual property: {currentPropertyName}]", thing.Name, propertyName, jsonProperty.Name); + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } } - if (result == SetPropertyResult.ReadOnly) - { - logger.LogInformation("Read-Only Property. [Thing Name: {thingName}][Property Name: {propertyName}]", thing.Name, property); - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; - } - - await context.WriteBodyAsync(HttpStatusCode.OK, thing.ThingContext.Properties.GetProperties(property), jsonOptions) + await context.WriteBodyAsync(HttpStatusCode.OK, new Dictionary {[propertyName] = property.GetValue() }, jsonOptions) .ConfigureAwait(false); } } diff --git a/src/Mozilla.IoT.WebThing/Event.cs b/src/Mozilla.IoT.WebThing/Event.cs deleted file mode 100644 index 460fe5c..0000000 --- a/src/Mozilla.IoT.WebThing/Event.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing -{ - public class Event - { - public Event(object data) - { - Data = data; - Timestamp = DateTime.UtcNow; - } - - public object Data { get; } - - public DateTime Timestamp { get;} - } -} diff --git a/src/Mozilla.IoT.WebThing/EventCollection.cs b/src/Mozilla.IoT.WebThing/EventCollection.cs deleted file mode 100644 index b83798e..0000000 --- a/src/Mozilla.IoT.WebThing/EventCollection.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Concurrent; - -namespace Mozilla.IoT.WebThing -{ - public class EventCollection - { - private readonly ConcurrentQueue _events; - private readonly object _locker = new object(); - private readonly int _size; - - public event EventHandler? Added; - - public EventCollection(int size) - { - _size = size; - _events = new ConcurrentQueue(); - } - - public void Enqueue(Event @event, string name) - { - if (_events.Count >= _size) - { - lock (_locker) - { - while (_events.Count >= _size) - { - _events.TryDequeue(out _); - } - } - } - - _events.Enqueue(@event); - - var add = Added; - add?.Invoke(name, @event); - } - - public void Dequeue() - { - _events.TryDequeue(out _); - } - - public Event[] ToArray() - { - return _events.ToArray(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Events/Event.cs b/src/Mozilla.IoT.WebThing/Events/Event.cs new file mode 100644 index 0000000..81e91b4 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Events/Event.cs @@ -0,0 +1,30 @@ +using System; + +namespace Mozilla.IoT.WebThing.Events +{ + /// + /// Represent raised event. + /// + public class Event + { + /// + /// Initialize a new instance of . + /// + /// The value raised. + public Event(object data) + { + Data = data; + Timestamp = DateTime.UtcNow; + } + + /// + /// Event value. + /// + public object Data { get; } + + /// + /// Datetime the event was raised. + /// + public DateTime Timestamp { get; } + } +} diff --git a/src/Mozilla.IoT.WebThing/Events/EventCollection.cs b/src/Mozilla.IoT.WebThing/Events/EventCollection.cs new file mode 100644 index 0000000..06779fb --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Events/EventCollection.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace Mozilla.IoT.WebThing.Events +{ + /// + /// Queue of + /// + public class EventCollection + { + private readonly ConcurrentQueue _events; + private readonly object _locker = new object(); + private readonly int _maxSize; + + /// + /// On event is added + /// + public event EventHandler? Added; + + /// + /// Initialize a new instance of . + /// + /// The max size of this collection. + public EventCollection(int maxSize) + { + _maxSize = maxSize; + _events = new ConcurrentQueue(); + } + + /// + /// Enqueue event. + /// + /// The to be enqueue. + /// The name of . + public void Enqueue(Event @event, string name) + { + if (_events.Count >= _maxSize) + { + lock (_locker) + { + while (_events.Count >= _maxSize) + { + _events.TryDequeue(out _); + } + } + } + + _events.Enqueue(@event); + + var add = Added; + add?.Invoke(name, @event); + } + + /// + /// Attempts to remove and return the object at the beginning of the . + /// + /// + /// When this method returns, if the operation was successful, contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// + /// + /// true if an element was removed and returned from the beginning of the successfully; otherwise, false. + /// + public bool TryDequeue([MaybeNullWhen(false)]out Event? @event) + => _events.TryDequeue(out @event); + + /// + /// Create event to array. + /// + /// New array of event. + public Event[] ToArray() + => _events.ToArray(); + } +} diff --git a/src/Mozilla.IoT.WebThing/Exceptions/PropertyNotFoundException.cs b/src/Mozilla.IoT.WebThing/Exceptions/PropertyNotFoundException.cs deleted file mode 100644 index e1c7fb6..0000000 --- a/src/Mozilla.IoT.WebThing/Exceptions/PropertyNotFoundException.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mozilla.IoT.WebThing.Exceptions -{ - public class PropertyNotFoundException : ThingException - { - public PropertyNotFoundException(string propertyName) - : base($"Property not found {propertyName}") - { - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Exceptions/ThingException.cs b/src/Mozilla.IoT.WebThing/Exceptions/ThingException.cs index 600a81c..93b03c2 100644 --- a/src/Mozilla.IoT.WebThing/Exceptions/ThingException.cs +++ b/src/Mozilla.IoT.WebThing/Exceptions/ThingException.cs @@ -1,24 +1,33 @@ using System; -using System.Runtime.Serialization; namespace Mozilla.IoT.WebThing.Exceptions { + /// + /// Base . + /// public class ThingException : Exception { + /// + /// Initialize a new instance of . + /// public ThingException() { } - protected ThingException(SerializationInfo? info, StreamingContext context) - : base(info, context) - { - } - + /// + /// Initialize a new instance of . + /// + /// The error message. public ThingException(string? message) : base(message) { } + /// + /// Initialize a new instance of . + /// + /// The error message. + /// The inner . public ThingException(string? message, Exception? innerException) : base(message, innerException) { diff --git a/src/Mozilla.IoT.WebThing/Extensions/IEndpointRouteBuilderExtensions.cs b/src/Mozilla.IoT.WebThing/Extensions/IEndpointRouteBuilderExtensions.cs index 3f2e598..af934f0 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/IEndpointRouteBuilderExtensions.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/IEndpointRouteBuilderExtensions.cs @@ -4,12 +4,21 @@ using Microsoft.Extensions.DependencyInjection; using Mozilla.IoT.WebThing; using Mozilla.IoT.WebThing.Endpoints; +using Mozilla.IoT.WebThing.Middlewares; using Mozilla.IoT.WebThing.WebSockets; namespace Microsoft.AspNetCore.Routing { + /// + /// + /// public static class IEndpointRouteBuilderExtensions { + /// + /// Map Things endpoints. + /// + /// + /// public static void MapThings(this IEndpointRouteBuilder endpoint) { if (endpoint == null) @@ -17,6 +26,9 @@ public static void MapThings(this IEndpointRouteBuilder endpoint) throw new ArgumentNullException(nameof(endpoint)); } + endpoint.CreateApplicationBuilder() + .UseMiddleware(); + endpoint.MapGet("/", GetAllThings.InvokeAsync); endpoint.MapGet("/things", GetAllThings.InvokeAsync); endpoint.MapGet("/things/{name}", context => context.WebSockets.IsWebSocketRequest diff --git a/src/Mozilla.IoT.WebThing/Extensions/ILGeneratorExtensions.cs b/src/Mozilla.IoT.WebThing/Extensions/ILGeneratorExtensions.cs new file mode 100644 index 0000000..b82321f --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Extensions/ILGeneratorExtensions.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; +using Mozilla.IoT.WebThing.Actions; + +namespace Mozilla.IoT.WebThing.Extensions +{ + internal static class ILGeneratorExtensions + { + private static readonly MethodInfo s_getService = typeof(IServiceProvider).GetMethod(nameof(IServiceProvider.GetService))!; + private static readonly MethodInfo s_getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!; + + private static readonly PropertyInfo s_getSource = typeof(ActionInfo).GetProperty("Source", BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly PropertyInfo s_getToken = typeof(CancellationTokenSource).GetProperty(nameof(CancellationTokenSource.Token), BindingFlags.Public | BindingFlags.Instance)!; + + private static readonly MethodInfo s_getItem = typeof(Dictionary).GetMethod("get_Item")!; + + #region Return + public static void Return(this ILGenerator generator, string? value) + { + if (value == null) + { + generator.Emit(OpCodes.Ldnull); + } + else + { + generator.Emit(OpCodes.Ldstr, value); + } + + generator.Emit(OpCodes.Ret); + } + + public static void Return(this ILGenerator generator, FieldBuilder field) + { + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldfld, field); + generator.Emit(OpCodes.Ret); + } + + public static void Return(this ILGenerator generator, LocalBuilder local) + { + generator.Emit(OpCodes.Ldloca_S, local.LocalIndex); + generator.Emit(OpCodes.Initobj, local.LocalType); + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ret); + } + + public static void Return(this ILGenerator generator, ConstructorInfo constructor) + { + generator.Emit(OpCodes.Newobj, constructor); + generator.Emit(OpCodes.Ret); + } + #endregion + + #region Set + + public static void Set(this ILGenerator generator, FieldBuilder field) + { + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Stfld, field); + generator.Emit(OpCodes.Ret); + } + + public static void SetProperty(this ILGenerator generator, PropertyInfo property) + { + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldstr, property.Name); + generator.EmitCall(OpCodes.Callvirt, s_getItem, null); + if (property.PropertyType.IsClass) + { + generator.Emit(OpCodes.Castclass, property.PropertyType); + } + else + { + generator.Emit(OpCodes.Unbox_Any, property.PropertyType); + } + generator.EmitCall(OpCodes.Callvirt, property.SetMethod!, null); + } + + + #endregion + + #region Cast + + public static void CastFirstArg(this ILGenerator generator, Type type) + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Castclass, type); + } + + #endregion + + #region Load + + public static void LoadFromService(this ILGenerator generator, Type parameterType) + { + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Ldtoken, parameterType); + generator.EmitCall(OpCodes.Call, s_getTypeFromHandle, null); + generator.EmitCall(OpCodes.Callvirt, s_getService, null); + generator.Emit(OpCodes.Castclass, parameterType); + } + + public static void LoadCancellationToken(this ILGenerator generator) + { + generator.Emit(OpCodes.Ldarg_0); + generator.EmitCall(OpCodes.Call, s_getSource.GetMethod!, null); + generator.EmitCall(OpCodes.Callvirt, s_getToken.GetMethod!, null); + } + + public static void LoadFromInput(this ILGenerator generator, MethodInfo getInput, MethodInfo getValue) + { + generator.Emit(OpCodes.Ldarg_0); + generator.EmitCall(OpCodes.Call, getInput, null); + generator.EmitCall(OpCodes.Callvirt, getValue, null); + } + + #endregion + + #region Call + + public static void Call(this ILGenerator generator, MethodInfo method) + { + if (method.DeclaringType!.IsClass) + { + generator.EmitCall(OpCodes.Callvirt, method, null); + } + else + { + generator.EmitCall(OpCodes.Call, method, null); + } + } + + #endregion + + #region NewObj + + public static void NewObj(this ILGenerator generator, ConstructorInfo constructor, bool addDup = false) + { + if (addDup) + { + generator.Emit(OpCodes.Dup); + } + + generator.Emit(OpCodes.Newobj, constructor); + } + #endregion + } +} diff --git a/src/Mozilla.IoT.WebThing/Extensions/IServiceExtensions.cs b/src/Mozilla.IoT.WebThing/Extensions/IServiceExtensions.cs index 43981a1..96c8575 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/IServiceExtensions.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/IServiceExtensions.cs @@ -3,15 +3,27 @@ using System.Linq; using System.Text.Json; using Microsoft.Extensions.DependencyInjection.Extensions; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Converts; using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; using Mozilla.IoT.WebThing.WebSockets; namespace Microsoft.Extensions.DependencyInjection { + /// + /// + /// public static class IServiceExtensions { - public static IThingCollectionBuilder AddThings(this IServiceCollection service, - Action? options = null) + /// + /// Add thing/. + /// + /// + /// + /// + /// The if null this will throw . + public static IThingCollectionBuilder AddThings(this IServiceCollection service, Action? options = null) { if (service == null) { @@ -31,17 +43,31 @@ public static IThingCollectionBuilder AddThings(this IServiceCollection service, PropertyNamingPolicy = opt.PropertyNamingPolicy, DictionaryKeyPolicy = opt.PropertyNamingPolicy, PropertyNameCaseInsensitive = opt.IgnoreCase, + Converters = + { + new ActionStatusConverter() + }, IgnoreNullValues = true }; }); + service.AddScoped(); + service.AddScoped(provider => provider.GetService().Observer); + service.AddSingleton(); service.AddSingleton(); service.AddSingleton(); - service.AddScoped(); - service.AddScoped(provider => provider.GetRequiredService().Observer); + service.AddTransient(); + service.AddTransient(); + service.AddTransient(); + service.AddTransient(); + service.AddTransient(); + + service.AddSingleton(); + service.AddSingleton(); + service.AddSingleton(provider => { var opt = provider.GetRequiredService(); @@ -58,9 +84,8 @@ public static IThingCollectionBuilder AddThings(this IServiceCollection service, } } - public class ThingObserverResolver + internal class ThingObservableResolver { - public ThingObserver Observer { get; set; } = default!; + public ThingObserver? Observer { get; set; } } - } diff --git a/src/Mozilla.IoT.WebThing/Extensions/IThingCollectionBuilder.cs b/src/Mozilla.IoT.WebThing/Extensions/IThingCollectionBuilder.cs index e9d8837..cd93dbb 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/IThingCollectionBuilder.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/IThingCollectionBuilder.cs @@ -1,10 +1,25 @@ namespace Mozilla.IoT.WebThing.Extensions { + /// + /// + /// public interface IThingCollectionBuilder { + /// + /// Add thing. + /// + /// The + /// Current . IThingCollectionBuilder AddThing() where T : Thing; + + /// + /// Add thing instance. + /// + /// The instance of . + /// The + /// Current . IThingCollectionBuilder AddThing(T thing) where T : Thing; diff --git a/src/Mozilla.IoT.WebThing/Extensions/StringExtensions.cs b/src/Mozilla.IoT.WebThing/Extensions/StringExtensions.cs index 3c21445..41e28d9 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/StringExtensions.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/StringExtensions.cs @@ -6,12 +6,12 @@ internal static class StringExtensions { public static string FirstCharToUpper(this string input) { - switch (input) + return input switch { - case null: throw new ArgumentNullException(nameof(input)); - case "": throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)); - default: return input[0].ToString().ToUpper() + input.Substring(1); - } + null => throw new ArgumentNullException(nameof(input)), + "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), + _ => input[0].ToString().ToUpper() + input.Substring(1) + }; } } } diff --git a/src/Mozilla.IoT.WebThing/Extensions/ThingCollectionBuilder.cs b/src/Mozilla.IoT.WebThing/Extensions/ThingCollectionBuilder.cs index 322dd30..2794b22 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/ThingCollectionBuilder.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/ThingCollectionBuilder.cs @@ -1,25 +1,20 @@ using System; -using System.Collections.Generic; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Mozilla.IoT.WebThing.Factories; -using Mozilla.IoT.WebThing.Factories.Generator.Actions; -using Mozilla.IoT.WebThing.Factories.Generator.Converter; -using Mozilla.IoT.WebThing.Factories.Generator.Events; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; -using Mozilla.IoT.WebThing.Factories.Generator.Properties; namespace Mozilla.IoT.WebThing.Extensions { + /// public class ThingCollectionBuilder : IThingCollectionBuilder { private readonly IServiceCollection _service; - public ThingCollectionBuilder(IServiceCollection service) + internal ThingCollectionBuilder(IServiceCollection service) { _service = service ?? throw new ArgumentNullException(nameof(service)); } + /// public IThingCollectionBuilder AddThing() where T : Thing { @@ -27,7 +22,8 @@ public IThingCollectionBuilder AddThing() _service.AddSingleton(ConfigureThing); return this; } - + + /// public IThingCollectionBuilder AddThing(T thing) where T : Thing { @@ -47,30 +43,10 @@ private static Thing ConfigureThing(IServiceProvider provider) { var thing = provider.GetRequiredService(); var option = provider.GetRequiredService(); - var optionsJson = new JsonSerializerOptions - { - WriteIndented = false, - IgnoreNullValues = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - var converter = new ConverterInterceptorFactory(thing, optionsJson); - var properties = new PropertiesInterceptFactory(thing, option); - var events = new EventInterceptFactory(thing, option); - var actions = new ActionInterceptFactory(option); - - CodeGeneratorFactory.Generate(thing, new List() - { - converter, - properties, - events, - actions - }); - - thing.ThingContext = new Context(converter.Create(), - properties.Create(), - events.Events, - actions.Actions); + var factory = provider.GetRequiredService(); + + thing.ThingContext = factory.Create(thing, option); + return thing; } diff --git a/src/Mozilla.IoT.WebThing/Extensions/ThingOption.cs b/src/Mozilla.IoT.WebThing/Extensions/ThingOption.cs index 179d055..411ae9e 100644 --- a/src/Mozilla.IoT.WebThing/Extensions/ThingOption.cs +++ b/src/Mozilla.IoT.WebThing/Extensions/ThingOption.cs @@ -3,16 +3,48 @@ namespace Mozilla.IoT.WebThing.Extensions { + /// + /// The thing option. + /// public class ThingOption { + /// + /// Max size of . + /// The default value is 10. + /// public int MaxEventSize { get; set; } = 10; + + /// + /// If should ignore case to deserialize. + /// The default value is true. + /// public bool IgnoreCase { get; set; } = true; + + + /// + /// + /// + public bool IgnoreNullValues { get; set; } = true; + + /// + /// If when serialize thing should serialize for use thing adapter. + /// The default value is false. + /// public bool UseThingAdapterUrl { get; set; } + + /// + /// Specifies the policy used to convert a property's name on an object to another format, such as camel-casing. + /// The resulting property name is expected to match the JSON payload during deserialization, and + /// will be used when writing the property name during serialization. + /// public JsonNamingPolicy PropertyNamingPolicy { get; set; } = JsonNamingPolicy.CamelCase; + + internal bool WriteIndented { get; set; } - private JsonSerializerOptions _options; + private JsonSerializerOptions? _options; private readonly object _locker = new object(); - public JsonSerializerOptions ToJsonSerializerOptions() + + internal JsonSerializerOptions ToJsonSerializerOptions() { if (_options == null) { @@ -24,17 +56,19 @@ public JsonSerializerOptions ToJsonSerializerOptions() { PropertyNamingPolicy = PropertyNamingPolicy, DictionaryKeyPolicy = PropertyNamingPolicy, - IgnoreNullValues = true, + IgnoreReadOnlyProperties = false, + IgnoreNullValues = IgnoreNullValues, + WriteIndented = WriteIndented, Converters = { - new ThingConverter(this) + new ActionStatusConverter() } }; } } } - return _options; + return _options!; } } } diff --git a/src/Mozilla.IoT.WebThing/Factories/CodeGeneratorFactory.cs b/src/Mozilla.IoT.WebThing/Factories/CodeGeneratorFactory.cs deleted file mode 100644 index 1534f57..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/CodeGeneratorFactory.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; -using Mozilla.IoT.WebThing.Factories.Generator.Visitor; - -namespace Mozilla.IoT.WebThing.Factories -{ - internal static class CodeGeneratorFactory - { - public static void Generate(Thing thing, IEnumerable factories) - { - var thingVisitor = factories - .Select(x => x.CreateThingIntercept()) - .ToArray(); - - foreach (var intercept in thingVisitor) - { - intercept.Before(thing); - } - - VisitProperties(thing, factories); - VisitActions(thing, factories); - VisitEvents(thing, factories); - - foreach (var intercept in thingVisitor) - { - intercept.After(thing); - } - } - - private static void VisitActions(Thing thing, IEnumerable factories) - { - var intercepts = factories - .Select(x => x.CreatActionIntercept()) - .ToArray(); - - ActionVisitor.Visit(intercepts, thing); - } - - private static void VisitProperties(Thing thing, IEnumerable factories) - { - var intercepts = factories - .Select(x => x.CreatePropertyIntercept()) - .ToArray(); - - PropertiesVisitor.Visit(intercepts, thing); - } - - private static void VisitEvents(Thing thing, IEnumerable factories) - { - var intercepts = factories - .Select(x => x.CreatEventIntercept()) - .ToArray(); - - EventVisitor.Visit(intercepts, thing); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionIntercept.cs deleted file mode 100644 index 2b244a4..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionIntercept.cs +++ /dev/null @@ -1,497 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Mozilla.IoT.WebThing.Actions; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Actions -{ - public class ActionIntercept : IActionIntercept - { - private static readonly MethodInfo s_getLength = typeof(string).GetProperty(nameof(string.Length)).GetMethod; - private static readonly MethodInfo s_match = typeof(Regex).GetMethod(nameof(Regex.Match) , new [] { typeof(string) }); - private static readonly MethodInfo s_success = typeof(Match).GetProperty(nameof(Match.Success)).GetMethod; - private static readonly ConstructorInfo s_regexConstructor = typeof(Regex).GetConstructors()[1]; - - private readonly ICollection<(string pattern, FieldBuilder field)> _regex = new LinkedList<(string pattern, FieldBuilder field)>(); - private const MethodAttributes s_getSetAttributes = - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; - - private readonly ModuleBuilder _moduleBuilder; - private readonly ThingOption _option; - public Dictionary Actions { get; } - - public ActionIntercept(ModuleBuilder moduleBuilder, ThingOption option) - { - _option = option; - _moduleBuilder = moduleBuilder; - Actions = option.IgnoreCase ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) - : new Dictionary(); - } - - public void Before(Thing thing) - { - } - - public void After(Thing thing) - { - } - - public void Intercept(Thing thing, MethodInfo action, ThingActionAttribute? actionInfo) - { - var thingType = thing.GetType(); - var inputBuilder = _moduleBuilder.DefineType($"{action.Name}Input", - TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass); - - var parameters = action.GetParameters(); - foreach (var parameter in parameters) - { - if (parameter.GetCustomAttribute() == null - && parameter.ParameterType != typeof(CancellationToken)) - { - CreateProperty(inputBuilder, parameter.Name!, parameter.ParameterType); - } - } - - var inputType = inputBuilder.CreateType()!; - - var actionBuilder = _moduleBuilder.DefineType($"{thingType.Name}{action.Name}ActionInfo", - TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass, - typeof(ActionInfo)); - var input = CreateProperty(actionBuilder, "input", inputType); - var name = actionInfo?.Name ?? action.Name; - CreateActionName(actionBuilder, name); - - var isValid = actionBuilder.DefineMethod("IsValid", - MethodAttributes.Private | MethodAttributes.Static, typeof(bool), - parameters - .Where(x => x.GetCustomAttribute() == null - && x.ParameterType != typeof(CancellationToken)) - .Select(x => x.ParameterType) - .ToArray()); - - var isValidIl = isValid.GetILGenerator(); - CreateParameterValidation(isValidIl, parameters, actionBuilder); - CreateInputValidation(actionBuilder, inputBuilder, isValid, input); - CreateExecuteAsync(actionBuilder, inputBuilder,input, action, thingType); - CreateStaticConstructor(actionBuilder); - - Actions.Add(_option.PropertyNamingPolicy.ConvertName(name), new ActionContext(actionBuilder.CreateType()!)); - } - - private void CreateStaticConstructor(TypeBuilder typeBuilder) - { - if (_regex.Count > 0) - { - var constructor = typeBuilder.DefineTypeInitializer(); - var il = constructor.GetILGenerator(); - - foreach (var (pattern, field) in _regex) - { - il.Emit(OpCodes.Ldstr, pattern); - il.Emit(OpCodes.Ldc_I4_8); - il.Emit(OpCodes.Newobj, s_regexConstructor); - il.Emit(OpCodes.Stsfld, field); - } - - il.Emit(OpCodes.Ret); - } - } - - private static PropertyBuilder CreateProperty(TypeBuilder builder, string fieldName, Type type) - { - var fieldBuilder = builder.DefineField($"_{fieldName}", type, FieldAttributes.Private); - var parameterName = fieldName.FirstCharToUpper(); - var propertyBuilder = builder.DefineProperty(parameterName, - PropertyAttributes.HasDefault, - type, null); - - var getProperty = builder.DefineMethod($"get_{parameterName}", s_getSetAttributes, - type, Type.EmptyTypes); - - var getPropertyIL = getProperty.GetILGenerator(); - getPropertyIL.Emit(OpCodes.Ldarg_0); - getPropertyIL.Emit(OpCodes.Ldfld, fieldBuilder); - getPropertyIL.Emit(OpCodes.Ret); - - // Define the "set" accessor method for CustomerName. - var setProperty = builder.DefineMethod($"set_{parameterName}", s_getSetAttributes, - null, new[] {type}); - - var setPropertyIL = setProperty.GetILGenerator(); - - setPropertyIL.Emit(OpCodes.Ldarg_0); - setPropertyIL.Emit(OpCodes.Ldarg_1); - setPropertyIL.Emit(OpCodes.Stfld, fieldBuilder); - setPropertyIL.Emit(OpCodes.Ret); - - propertyBuilder.SetGetMethod(getProperty); - propertyBuilder.SetSetMethod(setProperty); - - return propertyBuilder; - } - private static void CreateActionName(TypeBuilder builder, string value) - { - var propertyBuilder = builder.DefineProperty("ActionName", - PropertyAttributes.HasDefault | PropertyAttributes.SpecialName, - typeof(string), null); - - var getProperty = builder.DefineMethod("get_ActionName", - MethodAttributes.Family | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual, - typeof(string), Type.EmptyTypes); - - var getPropertyIL = getProperty.GetILGenerator(); - getPropertyIL.Emit(OpCodes.Ldstr, value); - getPropertyIL.Emit(OpCodes.Ret); - - propertyBuilder.SetGetMethod(getProperty); - } - - private static void CreateInputValidation(TypeBuilder builder, TypeBuilder input, MethodInfo isValid, PropertyBuilder inputProperty) - { - var isInputValidBuilder = builder.DefineMethod(nameof(ActionInfo.IsValid), - MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, - typeof(bool), Type.EmptyTypes); - - var isInputValid = isInputValidBuilder.GetILGenerator(); - - foreach (var property in input.GetProperties()) - { - isInputValid.Emit(OpCodes.Ldarg_0); - isInputValid.EmitCall(OpCodes.Call, inputProperty.GetMethod!, null); - isInputValid.EmitCall(OpCodes.Callvirt, property.GetMethod!, null ); - } - - isInputValid.EmitCall(OpCodes.Call, isValid, null); - isInputValid.Emit(OpCodes.Ret); - } - - private void CreateParameterValidation(ILGenerator il, ParameterInfo[] parameters, TypeBuilder typeBuilder) - { - Label? next = null; - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var validationParameter = parameter.GetCustomAttribute(); - - if (parameter.GetCustomAttribute() != null - || validationParameter == null - || parameter.ParameterType == typeof(CancellationToken)) - { - continue; - } - - var parameterType = parameter.ParameterType.GetUnderlyingType(); - - if (IsNumber(parameter.ParameterType)) - { - if (validationParameter.MinimumValue.HasValue) - { - var code = IsComplexNumber(parameterType) ? OpCodes.Bge_Un_S : OpCodes.Bge_S; - GenerateNumberValidation(il, i, parameterType, validationParameter.MinimumValue.Value, code, ref next); - } - - if (validationParameter.MaximumValue.HasValue) - { - var code = IsComplexNumber(parameterType) ? OpCodes.Ble_Un_S : OpCodes.Ble_S; - GenerateNumberValidation(il, i, parameterType, validationParameter.MaximumValue.Value, code, ref next); - } - - if (validationParameter.ExclusiveMinimumValue.HasValue) - { - var code = IsComplexNumber(parameterType) ? OpCodes.Bgt_Un_S : OpCodes.Bgt_S; - GenerateNumberValidation(il, i, parameterType, validationParameter.ExclusiveMinimumValue.Value, code, ref next); - } - - if (validationParameter.ExclusiveMaximumValue.HasValue) - { - var code = IsComplexNumber(parameterType) ? OpCodes.Blt_Un_S : OpCodes.Blt_S; - GenerateNumberValidation(il, i, parameterType, validationParameter.ExclusiveMaximumValue.Value, code, ref next); - } - - if (validationParameter.MultipleOfValue.HasValue) - { - if (next != null) - { - il.MarkLabel(next.Value); - } - - next = il.DefineLabel(); - - il.Emit(OpCodes.Ldarg_S, i); - SetValue(il, validationParameter.MultipleOfValue.Value, parameter.ParameterType); - - if (parameter.ParameterType == typeof(float) - || parameter.ParameterType == typeof(double) - || parameter.ParameterType == typeof(decimal)) - { - il.Emit(OpCodes.Rem); - if (parameter.ParameterType == typeof(float)) - { - il.Emit(OpCodes.Ldc_R4 , (float)0); - } - else - { - il.Emit(OpCodes.Ldc_R8, (double)0); - } - - il.Emit(OpCodes.Beq_S, next.Value); - } - else - { - il.Emit(parameter.ParameterType == typeof(ulong) ? OpCodes.Rem_Un : OpCodes.Rem); - il.Emit(OpCodes.Brfalse_S, next.Value); - } - - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ret); - } - } - else if (IsString(parameter.ParameterType)) - { - if (validationParameter.MinimumLengthValue.HasValue) - { - GenerateStringLengthValidation(il, i, validationParameter.MinimumLengthValue.Value, OpCodes.Bge_S, ref next); - } - - if (validationParameter.MaximumLengthValue.HasValue) - { - GenerateStringLengthValidation(il, i, validationParameter.MaximumLengthValue.Value, OpCodes.Ble_S, ref next); - } - - if (validationParameter.Pattern != null) - { - var regex = typeBuilder.DefineField($"_regex{parameter.Name}", typeof(Regex), - FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly); - _regex.Add((validationParameter.Pattern ,regex)); - if (next != null) - { - il.MarkLabel(next.Value); - } - - next = il.DefineLabel(); - var isNull = il.DefineLabel(); - il.Emit(OpCodes.Ldarg_S, i); - il.Emit(OpCodes.Brfalse_S, isNull); - - il.Emit(OpCodes.Ldsfld, regex); - il.Emit(OpCodes.Ldarg_S, i); - il.EmitCall(OpCodes.Callvirt, s_match, null); - il.EmitCall(OpCodes.Callvirt, s_success, null); - il.Emit(OpCodes.Brtrue_S, next.Value); - - il.MarkLabel(isNull); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ret); - } - } - } - - if (next.HasValue) - { - il.MarkLabel(next.Value); - } - - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Ret); - - static void GenerateNumberValidation(ILGenerator generator, int fieldIndex, Type fieldType, double value, OpCode code, ref Label? next) - { - if (next != null) - { - generator.MarkLabel(next.Value); - } - - next = generator.DefineLabel(); - - generator.Emit(OpCodes.Ldarg_S, fieldIndex); - SetValue(generator, value, fieldType); - - generator.Emit(code, next.Value); - - generator.Emit(OpCodes.Ldc_I4_0); - generator.Emit(OpCodes.Ret); - } - - static void GenerateStringLengthValidation(ILGenerator generator, int fieldIndex, uint value, OpCode code, ref Label? next) - { - if (next != null) - { - generator.MarkLabel(next.Value); - } - - next = generator.DefineLabel(); - - var nextCheckNull = generator.DefineLabel(); - - generator.Emit(OpCodes.Ldarg_S, fieldIndex); - generator.Emit(OpCodes.Brfalse_S, nextCheckNull); - - generator.Emit(OpCodes.Ldarg_S, fieldIndex); - generator.EmitCall(OpCodes.Callvirt, s_getLength, null); - generator.Emit(OpCodes.Ldc_I4, value); - generator.Emit(code, next.Value); - - generator.MarkLabel(nextCheckNull); - generator.Emit(OpCodes.Ldc_I4_0); - generator.Emit(OpCodes.Ret); - } - - static void SetValue(ILGenerator generator, double value, Type fieldType) - { - if (fieldType == typeof(byte)) - { - var convert = Convert.ToByte(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(sbyte)) - { - var convert = Convert.ToSByte(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(short)) - { - var convert = Convert.ToInt16(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(ushort)) - { - var convert = Convert.ToUInt16(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(int)) - { - var convert = Convert.ToInt32(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(uint)) - { - var convert = Convert.ToUInt32(value); - generator.Emit(OpCodes.Ldc_I4_S, convert); - } - else if (fieldType == typeof(long)) - { - var convert = Convert.ToInt64(value); - generator.Emit(OpCodes.Ldc_I8, convert); - } - else if (fieldType == typeof(ulong)) - { - var convert = Convert.ToUInt64(value); - if (convert <= uint.MaxValue) - { - generator.Emit(OpCodes.Ldc_I4_S, (int)convert); - generator.Emit(OpCodes.Conv_I8); - } - else - { - generator.Emit(OpCodes.Ldc_I8, convert); - } - } - else if (fieldType == typeof(float)) - { - var convert = Convert.ToSingle(value); - generator.Emit(OpCodes.Ldc_R4, convert); - } - else - { - var convert = Convert.ToDouble(value); - generator.Emit(OpCodes.Ldc_R8, convert); - } - } - - static bool IsComplexNumber(Type parameterType) - => parameterType == typeof(ulong) - || parameterType == typeof(float) - || parameterType == typeof(double) - || parameterType == typeof(decimal); - - static bool IsString(Type type) - => type == typeof(string); - - static bool IsNumber(Type type) - => type == typeof(int) - || type == typeof(uint) - || type == typeof(long) - || type == typeof(ulong) - || type == typeof(short) - || type == typeof(ushort) - || type == typeof(double) - || type == typeof(float) - || type == typeof(decimal) - || type == typeof(byte) - || type == typeof(sbyte); - } - - private static void CreateExecuteAsync(TypeBuilder builder, TypeBuilder inputBuilder, PropertyBuilder input, MethodInfo action, Type thingType) - { - var executeBuilder = builder.DefineMethod("InternalExecuteAsync", - MethodAttributes.Family | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, - typeof(ValueTask), new [] { typeof(Thing), typeof(IServiceProvider) }); - - var getService = typeof(IServiceProvider).GetMethod(nameof(IServiceProvider.GetService)); - var getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)); - - var getSource = typeof(ActionInfo).GetProperty("Source", BindingFlags.NonPublic | BindingFlags.Instance)!; - var getToken = typeof(CancellationTokenSource).GetProperty(nameof(CancellationTokenSource.Token), - BindingFlags.Public | BindingFlags.Instance)!; - - var il = executeBuilder.GetILGenerator(); - il.DeclareLocal(typeof(ValueTask)); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Castclass, thingType); - - - var inputProperties = inputBuilder.GetProperties(); - var counter = 0; - foreach (var parameter in action.GetParameters()) - { - if (parameter.GetCustomAttribute() != null) - { - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldtoken, parameter.ParameterType); - il.EmitCall(OpCodes.Call, getTypeFromHandle, null); - il.EmitCall(OpCodes.Callvirt, getService, null); - il.Emit(OpCodes.Castclass, parameter.ParameterType); - } - else if (parameter.ParameterType == typeof(CancellationToken)) - { - il.Emit(OpCodes.Ldarg_0); - il.EmitCall(OpCodes.Call, getSource.GetMethod!, null); - il.EmitCall(OpCodes.Callvirt, getToken.GetMethod!, null); - } - else - { - var property = inputProperties[counter++]; - il.Emit(OpCodes.Ldarg_0); - il.EmitCall(OpCodes.Call, input.GetMethod!, null); - il.EmitCall(OpCodes.Callvirt, property.GetMethod!, null); - } - } - - il.EmitCall(OpCodes.Callvirt, action, null); - if (action.ReturnType == typeof(void)) - { - il.Emit(OpCodes.Ldloca_S, 0); - il.Emit(OpCodes.Initobj, typeof(ValueTask)); - il.Emit(OpCodes.Ldloc_0); - } - else if(action.ReturnType == typeof(Task)) - { - var constructor = typeof(ValueTask).GetConstructor(new[] {typeof(Task)}); - il.Emit(OpCodes.Newobj, constructor); - } - - il.Emit(OpCodes.Ret); - } - - - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionInterceptFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionInterceptFactory.cs deleted file mode 100644 index 966cc84..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Actions/ActionInterceptFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Actions -{ - public class ActionInterceptFactory : IInterceptorFactory - { - private readonly ActionIntercept _intercept; - - public Dictionary Actions => _intercept.Actions; - public ActionInterceptFactory(ThingOption option) - { - var assemblyName = new AssemblyName("ActionAssembly"); - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("ActionModule"); - _intercept = new ActionIntercept(moduleBuilder, option); - } - - public IThingIntercept CreateThingIntercept() - => new EmptyIntercept(); - - public IPropertyIntercept CreatePropertyIntercept() - => new EmptyIntercept(); - - public IActionIntercept CreatActionIntercept() - => _intercept; - - public IEventIntercept CreatEventIntercept() - => new EmptyIntercept(); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertActionIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertActionIntercept.cs deleted file mode 100644 index 0f846d5..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertActionIntercept.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Reflection; -using System.Text.Json; -using System.Threading; -using Microsoft.AspNetCore.Mvc; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -using static Mozilla.IoT.WebThing.Factories.Generator.Converter.Helper; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class ConvertActionIntercept : IActionIntercept - { - private readonly Utf8JsonWriterILGenerator _jsonWriter; - private readonly JsonSerializerOptions _options; - private bool _isObjectStart; - - public ConvertActionIntercept(Utf8JsonWriterILGenerator jsonWriter, JsonSerializerOptions options) - { - _jsonWriter = jsonWriter ?? throw new ArgumentNullException(nameof(jsonWriter)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - - public void Before(Thing thing) - { - - } - - public void After(Thing thing) - { - if (_isObjectStart) - { - _jsonWriter.EndObject(); - } - else - { - _jsonWriter.PropertyWithNullValue("Actions"); - } - } - - public void Intercept(Thing thing, MethodInfo action, ThingActionAttribute? actionInfo) - { - if (!_isObjectStart) - { - _jsonWriter.StartObject("Actions"); - _isObjectStart = true; - } - - var name = actionInfo?.Name ?? action.Name; - - _jsonWriter.StartObject(name); - - if (actionInfo != null) - { - _jsonWriter.PropertyWithNullableValue("Title", actionInfo.Title); - _jsonWriter.PropertyWithNullableValue("Description", actionInfo.Description); - _jsonWriter.PropertyType("@type", actionInfo.Type); - } - - var parameters = action.GetParameters(); - - if (parameters.Length > 0) - { - _jsonWriter.StartObject("Input"); - - _jsonWriter.PropertyWithValue("Type", "object"); - - _jsonWriter.StartObject("Properties"); - foreach (var parameter in parameters) - { - if (parameter.GetCustomAttribute() != null - || parameter.ParameterType == typeof(CancellationToken)) - { - continue; - } - - _jsonWriter.StartObject(parameter.Name!); - var parameterType = parameter.ParameterType.GetUnderlyingType(); - var jsonType = GetJsonType(parameterType); - - if (jsonType == null) - { - throw new ArgumentException(); - } - - _jsonWriter.PropertyWithValue("Type", jsonType); - var parameterActionInfo = parameter.GetCustomAttribute(); - - if (parameterActionInfo != null) - { - _jsonWriter.PropertyWithNullableValue("Title", parameterActionInfo.Title); - _jsonWriter.PropertyWithNullableValue("Description", parameterActionInfo.Description); - _jsonWriter.PropertyWithNullableValue("Unit", parameterActionInfo.Unit); - if (jsonType == "number" || jsonType == "integer") - { - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.Minimum), parameterType, - parameterActionInfo.MinimumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.Maximum), parameterType, - parameterActionInfo.MaximumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.ExclusiveMinimum), parameterType, - parameterActionInfo.ExclusiveMinimumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.ExclusiveMaximum), parameterType, - parameterActionInfo.ExclusiveMaximumValue); - _jsonWriter.PropertyWithNullableValue(nameof(ThingPropertyAttribute.MultipleOf), - parameterActionInfo.MultipleOfValue); - } - else if (jsonType == "string") - { - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.MinimumLength), parameterType, - parameterActionInfo.MinimumLengthValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.MaximumLength), parameterType, - parameterActionInfo.MaximumLengthValue); - _jsonWriter.PropertyString(nameof(ThingPropertyAttribute.Pattern), parameterType, - parameterActionInfo.Pattern); - } - } - - _jsonWriter.EndObject(); - } - - _jsonWriter.EndObject(); - _jsonWriter.EndObject(); - } - else if (actionInfo?.Type != null) - { - _jsonWriter.StartObject("Input"); - _jsonWriter.PropertyType("@type", actionInfo.Type); - _jsonWriter.EndObject(); - } - - _jsonWriter.StartArray("Links"); - _jsonWriter.StartObject(); - _jsonWriter.PropertyWithValue("href", - $"/things/{_options.GetPropertyName(thing.Name)}/actions/{_options.GetPropertyName(name)}"); - _jsonWriter.EndObject(); - _jsonWriter.EndArray(); - _jsonWriter.EndObject(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertEventIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertEventIntercept.cs deleted file mode 100644 index 7b67895..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConvertEventIntercept.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Reflection; -using System.Text.Json; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -using static Mozilla.IoT.WebThing.Factories.Generator.Converter.Helper; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class ConvertEventIntercept : IEventIntercept - { - private readonly Utf8JsonWriterILGenerator _jsonWriter; - private readonly JsonSerializerOptions _options; - private bool _isObjectStart; - - public ConvertEventIntercept(Utf8JsonWriterILGenerator jsonWriter, JsonSerializerOptions options) - { - _jsonWriter = jsonWriter ?? throw new ArgumentNullException(nameof(jsonWriter)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public void Before(Thing thing) - { - - } - - public void After(Thing thing) - { - if (_isObjectStart) - { - _jsonWriter.EndObject(); - } - else - { - _jsonWriter.PropertyWithNullValue("Events"); - } - } - - public void Visit(Thing thing, EventInfo @event, ThingEventAttribute? eventInfo) - { - if (!_isObjectStart) - { - _jsonWriter.StartObject("Events"); - _isObjectStart = true; - } - - var name = eventInfo?.Name ?? @event.Name; - _jsonWriter.StartObject(name); - - if (eventInfo != null) - { - _jsonWriter.PropertyWithNullableValue(nameof(ThingEventAttribute.Title), eventInfo.Title); - _jsonWriter.PropertyWithNullableValue(nameof(ThingEventAttribute.Description), eventInfo.Description); - _jsonWriter.PropertyWithNullableValue(nameof(ThingEventAttribute.Unit), eventInfo.Unit); - _jsonWriter.PropertyType("@type", eventInfo.Type); - } - - _jsonWriter.PropertyWithNullableValue("type", GetJsonType(GetEventType(@event.EventHandlerType))); - - _jsonWriter.StartArray("Links"); - _jsonWriter.StartObject(); - - _jsonWriter.PropertyWithValue( "href", $"/things/{_options.GetPropertyName(thing.Name)}/events/{_options.GetPropertyName(name)}"); - - _jsonWriter.EndObject(); - _jsonWriter.EndArray(); - - _jsonWriter.EndObject(); - - static Type? GetEventType(Type? eventHandlerType) - { - if (eventHandlerType == null - || eventHandlerType.GenericTypeArguments.Length != 1) - { - return null; - } - return eventHandlerType.GenericTypeArguments[0]; - } - } - - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterInterceptorFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterInterceptorFactory.cs deleted file mode 100644 index 49b1079..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterInterceptorFactory.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; -using System.Text.Json; -using Mozilla.IoT.WebThing.Converts; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class ConverterInterceptorFactory : IInterceptorFactory - { - private readonly JsonSerializerOptions _options; - private readonly TypeBuilder _builder; - private readonly Utf8JsonWriterILGenerator _jsonWriterIlGenerator; - private readonly ILGenerator _il; - - public ConverterInterceptorFactory(Thing thing, JsonSerializerOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - - var thingType = thing.GetType(); - _builder = Factory.CreateTypeBuilder($"{thingType.Name}Converter", thingType.Name, - typeof(IThingConverter), TypeAttributes.AutoClass | TypeAttributes.Class | TypeAttributes.Public); - - var methodBuilder = _builder.DefineMethod(nameof(IThingConverter.Write), - MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual, - typeof(void), - new[] { typeof(Utf8JsonWriter), typeof(Thing), typeof(JsonSerializerOptions) }); - - _il = methodBuilder.GetILGenerator(); - _jsonWriterIlGenerator = new Utf8JsonWriterILGenerator(_il, _options); - } - - public IThingIntercept CreateThingIntercept() - => new ConverterThingIntercept(_jsonWriterIlGenerator); - - public IPropertyIntercept CreatePropertyIntercept() - => new ConverterPropertyIntercept(_jsonWriterIlGenerator, _options); - - public IActionIntercept CreatActionIntercept() - => new ConvertActionIntercept(_jsonWriterIlGenerator, _options); - - public IEventIntercept CreatEventIntercept() - => new ConvertEventIntercept(_jsonWriterIlGenerator, _options); - - public IThingConverter Create() - { - _il.Emit(OpCodes.Ret); - return (IThingConverter)Activator.CreateInstance(_builder.CreateType()!)!; - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterPropertyIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterPropertyIntercept.cs deleted file mode 100644 index ec89cc0..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterPropertyIntercept.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Reflection; -using System.Text.Json; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -using static Mozilla.IoT.WebThing.Factories.Generator.Converter.Helper; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class ConverterPropertyIntercept : IPropertyIntercept - { - private readonly Utf8JsonWriterILGenerator _jsonWriter; - private readonly JsonSerializerOptions _options; - private bool _isObjectStart; - - public ConverterPropertyIntercept(Utf8JsonWriterILGenerator jsonWriter, JsonSerializerOptions options) - { - _jsonWriter = jsonWriter ?? throw new ArgumentNullException(nameof(jsonWriter)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public void Before(Thing thing) - { - - } - - public void After(Thing thing) - { - if (_isObjectStart) - { - _jsonWriter.EndObject(); - } - else - { - _jsonWriter.PropertyWithNullValue("Properties"); - } - } - - public void Intercept(Thing thing, PropertyInfo propertyInfo, ThingPropertyAttribute? thingPropertyAttribute) - { - if (!_isObjectStart) - { - _jsonWriter.StartObject("Properties"); - _isObjectStart = true; - } - - var propertyName = _options.GetPropertyName(thingPropertyAttribute?.Name ?? propertyInfo.Name); - var propertyType = propertyInfo.PropertyType.GetUnderlyingType(); - var jsonType = GetJsonType(propertyType); - if (jsonType == null) - { - return; - } - - _jsonWriter.StartObject(propertyName); - - if (thingPropertyAttribute != null) - { - _jsonWriter.PropertyWithNullableValue(nameof(ThingPropertyAttribute.Title), - thingPropertyAttribute.Title); - _jsonWriter.PropertyWithNullableValue(nameof(ThingPropertyAttribute.Description), - thingPropertyAttribute.Description); - var readOnly = thingPropertyAttribute.IsReadOnly || !propertyInfo.CanWrite || !propertyInfo.SetMethod.IsPublic; - _jsonWriter.PropertyWithNullableValue("ReadOnly", readOnly); - - if (thingPropertyAttribute.IsWriteOnlyValue.HasValue) - { - _jsonWriter.PropertyWithNullableValue("WriteOnly", thingPropertyAttribute.IsWriteOnlyValue.Value); - } - else if(!propertyInfo.CanRead || !propertyInfo.GetMethod.IsPublic) - { - _jsonWriter.PropertyWithNullableValue("WriteOnly", true); - } - - - _jsonWriter.PropertyWithNullableValue("Type", jsonType); - _jsonWriter.PropertyEnum("@enum", propertyType, thingPropertyAttribute.Enum); - _jsonWriter.PropertyWithNullableValue(nameof(ThingPropertyAttribute.Unit), thingPropertyAttribute.Unit); - _jsonWriter.PropertyType("@type", thingPropertyAttribute.Type); - - if (jsonType == "number" || jsonType == "integer") - { - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.Minimum), propertyType, - thingPropertyAttribute.MinimumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.Maximum), propertyType, - thingPropertyAttribute.MaximumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.ExclusiveMinimum), propertyType, - thingPropertyAttribute.ExclusiveMinimumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.ExclusiveMaximum), propertyType, - thingPropertyAttribute.ExclusiveMaximumValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.MultipleOf), propertyType, - thingPropertyAttribute.MultipleOfValue); - } - else if (jsonType == "string") - { - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.MinimumLength), propertyType, - thingPropertyAttribute.MinimumLengthValue); - _jsonWriter.PropertyNumber(nameof(ThingPropertyAttribute.MaximumLength), propertyType, - thingPropertyAttribute.MaximumLengthValue); - _jsonWriter.PropertyString(nameof(ThingPropertyAttribute.Pattern), propertyType, - thingPropertyAttribute.Pattern); - } - } - else - { - if (!propertyInfo.CanWrite || !propertyInfo.SetMethod.IsPublic) - { - _jsonWriter.PropertyWithNullableValue("ReadOnly", true); - } - - if (!propertyInfo.CanRead || !propertyInfo.GetMethod.IsPublic) - { - _jsonWriter.PropertyWithNullableValue("WriteOnly", true); - } - } - - - _jsonWriter.StartArray("Links"); - - _jsonWriter.StartObject(); - - _jsonWriter.PropertyWithValue("href", - $"/things/{_options.GetPropertyName(thing.Name)}/properties/{propertyName}"); - - _jsonWriter.EndObject(); - _jsonWriter.EndArray(); - - _jsonWriter.EndObject(); - } - - - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterThingIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterThingIntercept.cs deleted file mode 100644 index 466189c..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/ConverterThingIntercept.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class ConverterThingIntercept : IThingIntercept - { - private readonly Utf8JsonWriterILGenerator _jsonWriter; - - public ConverterThingIntercept(Utf8JsonWriterILGenerator jsonWriter) - { - _jsonWriter = jsonWriter ?? throw new ArgumentNullException(nameof(jsonWriter)); - } - - public void Before(Thing thing) - { - _jsonWriter.PropertyWithNullableValue(nameof(Thing.Title), thing.Title); - _jsonWriter.PropertyWithNullableValue(nameof(Thing.Description), thing.Description); - _jsonWriter.PropertyType("@type", thing.Type); - } - - public void After(Thing thing) - { - - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Helper.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Helper.cs deleted file mode 100644 index 4c42027..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Helper.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal static class Helper - { - public static string? GetJsonType(Type? type) - { - if (type == null) - { - return null; - } - - type = Nullable.GetUnderlyingType(type) ?? type; - - if (type == typeof(string) - || type == typeof(DateTime) - || type == typeof(DateTimeOffset)) - { - return "string"; - } - - if (type == typeof(bool)) - { - return "boolean"; - } - - if (type == typeof(int) - || type == typeof(sbyte) - || type == typeof(byte) - || type == typeof(short) - || type == typeof(long) - || type == typeof(uint) - || type == typeof(ulong) - || type == typeof(ushort)) - { - return "integer"; - } - - if (type == typeof(double) - || type == typeof(float) - || type == typeof(decimal)) - { - return "number"; - } - - return null; - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Utf8JsonWriterILGenerator.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Utf8JsonWriterILGenerator.cs deleted file mode 100644 index be29559..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Converter/Utf8JsonWriterILGenerator.cs +++ /dev/null @@ -1,662 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Converter -{ - internal class Utf8JsonWriterILGenerator - { - private readonly ILGenerator _ilGenerator; - private readonly JsonSerializerOptions _options; - - public Utf8JsonWriterILGenerator(ILGenerator ilGenerator, JsonSerializerOptions options) - { - _ilGenerator = ilGenerator ?? throw new ArgumentNullException(nameof(ilGenerator)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - #region Types Functions - private readonly MethodInfo s_writeStartObjectWithName = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteStartObject), new [] { typeof(string)} )!; - private readonly MethodInfo s_writeStartObject = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteStartObject), new Type[0])!; - private readonly MethodInfo s_writeEndObject = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteEndObject), new Type[0])!; - - private readonly MethodInfo s_writeStartArray = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteStartArray), new [] { typeof(string)})!; - private readonly MethodInfo s_writeEndArray = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteEndArray), new Type[0])!; - - private readonly MethodInfo s_writeString = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteString), new []{ typeof(string), typeof(string) })!; - private readonly MethodInfo s_writeStringValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteStringValue), new []{ typeof(string) })!; - private readonly MethodInfo s_writeBool = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteBoolean), new []{ typeof(string), typeof(bool) })!; - private readonly MethodInfo s_writeBoolValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteBooleanValue), new []{ typeof(bool) })!; - private readonly MethodInfo s_writeNull = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNull), new []{ typeof(string) })!; - private readonly MethodInfo s_writeNullValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNullValue), new Type[0])!; - - private readonly MethodInfo s_writeNumberInt = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(int) })!; - private readonly MethodInfo s_writeNumberUInt = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(uint) })!; - private readonly MethodInfo s_writeNumberLong = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(long) })!; - private readonly MethodInfo s_writeNumberULong = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(ulong) })!; - private readonly MethodInfo s_writeNumberDouble = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(double) })!; - private readonly MethodInfo s_writeNumberFloat = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumber), new [] { typeof(string), typeof(float) })!; - - private readonly MethodInfo s_writeNumberIntValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(int) })!; - private readonly MethodInfo s_writeNumberUIntValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(uint) })!; - private readonly MethodInfo s_writeNumberLongValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(long) })!; - private readonly MethodInfo s_writeNumberULongValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(ulong) })!; - private readonly MethodInfo s_writeNumberDoubleValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(double) })!; - private readonly MethodInfo s_writeNumberFloatValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(float) })!; - private readonly MethodInfo s_writeNumberDecimalValue = typeof(Utf8JsonWriter).GetMethod(nameof(Utf8JsonWriter.WriteNumberValue), new []{ typeof(decimal) })!; - - private readonly MethodInfo s_convertULong = typeof(Convert).GetMethod(nameof(Convert.ToUInt64), new []{ typeof(string) })!; - private readonly MethodInfo s_convertDecimal = typeof(Convert).GetMethod(nameof(Convert.ToDecimal), new []{ typeof(string) })!; - #endregion - - #region Object - - public void StartObject(string propertyName) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeStartObjectWithName, new[] { typeof(string) }); - } - - public void StartObject() - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeStartObject, null); - } - - public void EndObject() - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeEndObject, null); - } - - #endregion - - #region Array - - public void StartArray(string propertyName) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeStartArray, new[] { typeof(string) }); - } - - public void EndArray() - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeEndArray, null); - } - - #endregion - - #region Properties - - public void PropertyWithNullValue(string propertyName) - { - if (!_options.IgnoreNullValues) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNull, new[] {typeof(string)}); - } - } - - public void PropertyWithValue(string propertyName, string value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr,_options.PropertyNamingPolicy.ConvertName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldstr, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeString, new[] { typeof(string), typeof(string) }); - } - - public void PropertyWithValue(string propertyName, bool value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.PropertyNamingPolicy.ConvertName(propertyName)); - _ilGenerator.Emit(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeBool, new[] { typeof(string), typeof(bool) }); - } - - #region Number - - public void PropertyWithValue(string propertyName, int value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldc_I4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberInt, new[] { typeof(string), typeof(int) }); - } - - public void PropertyWithValue(string propertyName, uint value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldc_I4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberUInt, new[] { typeof(string), typeof(uint) }); - } - - public void PropertyWithValue(string propertyName, long value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldc_I8, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberLong, new[] { typeof(string), typeof(long) }); - } - - public void PropertyWithValue(string propertyName, ulong value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - if (value > long.MaxValue) - { - _ilGenerator.Emit(OpCodes.Ldstr, value.ToString()); - _ilGenerator.EmitCall(OpCodes.Call, s_convertULong, null); - } - else - { - _ilGenerator.Emit(OpCodes.Ldc_I8, Convert.ToInt64(value)); - } - - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberULong, new[] { typeof(string), typeof(long) }); - } - - public void PropertyWithValue(string propertyName, double value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldc_R8, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberDouble, new[] { typeof(string), typeof(double) }); - } - - public void PropertyWithValue(string propertyName, float value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, _options.GetPropertyName(propertyName)); - _ilGenerator.Emit(OpCodes.Ldc_R4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberFloat, new[] { typeof(string), typeof(float) }); - } - - #endregion - - #endregion - - #region Properties With Nullable Valeues - - public void PropertyWithNullableValue(string propertyName, string? value) - { - if (value == null) - { - PropertyWithNullValue(propertyName); - } - else - { - PropertyWithValue(propertyName, value); - } - } - - public void PropertyWithNullableValue(string propertyName, bool? value) - { - if (value.HasValue) - { - PropertyWithValue(propertyName, value.Value); - } - else - { - PropertyWithNullValue(propertyName); - } - } - - public void PropertyWithNullableValue(string propertyName, int? value) - { - if (value.HasValue) - { - PropertyWithValue(propertyName, value.Value); - } - else - { - PropertyWithNullValue(propertyName); - } - } - - #endregion - - #region Value - - public void NullValue() - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNullValue, null); - } - - public void Value(string value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeStringValue, null); - } - - public void Value(bool value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(value ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeBoolValue, null); - } - - public void Value(int value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit( OpCodes.Ldc_I4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberIntValue, null); - } - - public void Value(uint value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit( OpCodes.Ldc_I4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberUIntValue, null); - } - - - public void Value(long value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit( OpCodes.Ldc_I8, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberLongValue, null); - } - - public void Value(ulong value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - if (value > long.MaxValue) - { - _ilGenerator.Emit(OpCodes.Ldstr, value.ToString()); - _ilGenerator.EmitCall(OpCodes.Call, s_convertULong, null); - } - else - { - _ilGenerator.Emit(OpCodes.Ldc_I8, Convert.ToInt64(value)); - } - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberULongValue, null); - } - - public void Value(float value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit( OpCodes.Ldc_R4, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberFloatValue, null); - } - - public void Value(double value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit( OpCodes.Ldc_R8, value); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberDoubleValue, null); - } - - public void Value(decimal value) - { - _ilGenerator.Emit(OpCodes.Ldarg_1); - _ilGenerator.Emit(OpCodes.Ldstr, value.ToString(CultureInfo.InvariantCulture)); - _ilGenerator.EmitCall(OpCodes.Call, s_convertDecimal, null); - _ilGenerator.EmitCall(OpCodes.Callvirt, s_writeNumberDecimalValue, null); - } - #endregion - - public void PropertyType(string propertyName, string[]? types) - { - if (types == null) - { - PropertyWithNullValue(propertyName); - } - else if (types.Length == 1) - { - PropertyWithValue(propertyName, types[0]); - } - else - { - StartArray(propertyName); - - foreach (var value in types) - { - if (value == null) - { - NullValue(); - } - else - { - Value(value); - } - } - - EndArray(); - } - } - - public void PropertyNumber(string propertyName, Type propertyType, double? value) - { - if (value == null) - { - PropertyWithNullValue(propertyName); - return; - } - - if (propertyType == typeof(int) - || propertyType == typeof(sbyte) - || propertyType == typeof(byte) - || propertyType == typeof(short) - || propertyType == typeof(ushort)) - { - PropertyWithValue(propertyName, Convert.ToInt32(value)); - } - else if (propertyType == typeof(uint)) - { - PropertyWithValue(propertyName, Convert.ToUInt32(value)); - } - else if (propertyType == typeof(long)) - { - PropertyWithValue(propertyName, Convert.ToInt64(value)); - } - else if (propertyType == typeof(ulong)) - { - PropertyWithValue(propertyName, Convert.ToUInt64(value)); - } - else if (propertyType == typeof(double)) - { - PropertyWithValue(propertyName, value.Value); - } - else - { - PropertyWithValue(propertyName, Convert.ToSingle(value)); - } - } - - public void PropertyEnum(string propertyName, Type propertyType, object[]? @enums) - { - if (enums == null) - { - PropertyWithNullValue(propertyName); - return; - } - - StartArray(propertyName); - - var set = new HashSet(); - - if (propertyType == typeof(string)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToString(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(bool)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToBoolean(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(sbyte)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToSByte(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(byte)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToByte(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(short)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToInt16(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(ushort)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToUInt16(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(int)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToInt32(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(Convert.ToInt32(@enum)); - } - } - } - else if (propertyType == typeof(uint)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToUInt32(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(long)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToInt64(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(ulong)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToUInt64(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(double)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToDouble(@enum); - if (!set.Add(value)) - { - continue; - } - - Value(value); - } - } - } - else if (propertyType == typeof(float)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToSingle(@enum); - if (!set.Add(value)) - { - continue; - } - Value(value); - } - } - } - else if (propertyType == typeof(decimal)) - { - foreach (var @enum in enums) - { - if (@enum == null) - { - NullValue(); - } - else - { - var value = Convert.ToDecimal(@enum); - if (!set.Add(value)) - { - continue; - } - Value(value); - } - } - } - - EndArray(); - } - - public void PropertyString(string propertyName, Type propertyType, string? value) - { - if (value == null) - { - PropertyWithNullValue(propertyName); - return; - } - - PropertyWithValue(propertyName, value); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/EmptyIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/EmptyIntercept.cs deleted file mode 100644 index 924a600..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/EmptyIntercept.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator -{ - public class EmptyIntercept : IThingIntercept, IActionIntercept, IEventIntercept, IPropertyIntercept - { - public void Before(Thing thing) - { - - } - public void After(Thing thing) - { - } - - public void Visit(Thing thing, EventInfo @event, ThingEventAttribute? eventInfo) - { - } - - public void Intercept(Thing thing, MethodInfo action, ThingActionAttribute? actionInfo) - { - - } - - public void Intercept(Thing thing, PropertyInfo propertyInfo, ThingPropertyAttribute? thingPropertyAttribute) - { - - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventIntercept.cs deleted file mode 100644 index e7d4551..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventIntercept.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Events -{ - public class EventIntercept : IEventIntercept - { - public Dictionary Events { get; } - private readonly Queue _eventToBind = new Queue(); - - private readonly ConstructorInfo _createThing = typeof(Event).GetConstructors(BindingFlags.Public | BindingFlags.Instance)[0]; - private readonly MethodInfo _getContext = typeof(Thing).GetProperty(nameof(Thing.ThingContext))?.GetMethod!; - private readonly MethodInfo _getEvent = typeof(Context).GetProperty(nameof(Context.Events))?.GetMethod!; - private readonly MethodInfo _getItem = typeof(Dictionary).GetMethod("get_Item")!; - private readonly MethodInfo _addItem = typeof(EventCollection).GetMethod(nameof(EventCollection.Enqueue))!; - private readonly ThingOption _options; - private readonly TypeBuilder _builder; - - public EventIntercept(TypeBuilder builder, ThingOption options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _builder = builder ?? throw new ArgumentNullException(nameof(builder)); - - Events = options.IgnoreCase ? new Dictionary(StringComparer.OrdinalIgnoreCase) - : new Dictionary(); - } - - public void Before(Thing thing) - { - - } - - public void After(Thing thing) - { - var type = _builder.CreateType()!; - while (_eventToBind.TryDequeue(out var @event)) - { - var @delegate = Delegate.CreateDelegate(@event.EventHandlerType!, type, $"{@event.Name}Handler"); - @event.AddEventHandler(thing, @delegate ); - } - } - - public void Visit(Thing thing, EventInfo @event, ThingEventAttribute? eventInfo) - { - _eventToBind.Enqueue(@event); - var name = eventInfo?.Name ?? @event.Name; - Events.Add(_options.PropertyNamingPolicy.ConvertName(name), new EventCollection(_options.MaxEventSize)); - - var type = @event.EventHandlerType?.GetGenericArguments()[0]!; - var methodBuilder =_builder.DefineMethod($"{@event.Name}Handler", - MethodAttributes.Public | MethodAttributes.Static); - - methodBuilder.SetParameters(typeof(object), type); - - var il = methodBuilder.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Castclass, thing.GetType()); - il.EmitCall(OpCodes.Callvirt, _getContext, null); - il.EmitCall(OpCodes.Callvirt, _getEvent, null); - il.Emit(OpCodes.Ldstr, name); - il.EmitCall(OpCodes.Callvirt, _getItem, null); - il.Emit(OpCodes.Ldarg_1); - - if (type.IsValueType) - { - il.Emit(OpCodes.Box, type); - } - - il.Emit(OpCodes.Newobj, _createThing); - il.Emit(OpCodes.Ldstr, _options.PropertyNamingPolicy.ConvertName(name)); - il.EmitCall(OpCodes.Callvirt, _addItem, null); - il.Emit(OpCodes.Ret); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventInterceptFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventInterceptFactory.cs deleted file mode 100644 index 6e59d85..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Events/EventInterceptFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Events -{ - public class EventInterceptFactory : IInterceptorFactory - { - private readonly EventIntercept _intercept; - - public EventInterceptFactory(Thing thing, ThingOption options) - { - var thingType = thing.GetType(); - var builder = Factory.CreateTypeBuilder($"{thingType.Name}EventBinder", thingType.Name, - null, TypeAttributes.AutoClass | TypeAttributes.Class | TypeAttributes.Public); - - _intercept = new EventIntercept(builder, options); - } - - public Dictionary Events => _intercept.Events; - - public IThingIntercept CreateThingIntercept() - => new EmptyIntercept(); - - public IPropertyIntercept CreatePropertyIntercept() - => new EmptyIntercept(); - - public IActionIntercept CreatActionIntercept() - => new EmptyIntercept(); - - public IEventIntercept CreatEventIntercept() - => _intercept; - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Factory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Factory.cs deleted file mode 100644 index cee897c..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Factory.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; - -namespace Mozilla.IoT.WebThing.Factories.Generator -{ - internal class Factory - { - public static TypeBuilder CreateTypeBuilder(string typeName, string baseName, Type? @interface, TypeAttributes typeAttributes) - { - var assemblyName = new AssemblyName($"{baseName}Assembly"); - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - var moduleBuilder = assemblyBuilder.DefineDynamicModule($"{typeName}Module"); - Type[]? interfaces = null; - if (@interface != null) - { - interfaces = new[] { @interface }; - } - - return moduleBuilder.DefineType(typeName, typeAttributes, null, interfaces); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IActionIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IActionIntercept.cs deleted file mode 100644 index 0a3ed76..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IActionIntercept.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IActionIntercept : IIntercept - { - void Intercept(Thing thing, MethodInfo action, ThingActionAttribute? actionInfo); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IEventIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IEventIntercept.cs deleted file mode 100644 index 182ba59..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IEventIntercept.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IEventIntercept : IIntercept - { - void Visit(Thing thing, EventInfo @event, ThingEventAttribute? eventInfo); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IIntercept.cs deleted file mode 100644 index a78b47a..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IIntercept.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IIntercept - { - void Before(Thing thing); - void After(Thing thing); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IInterceptorFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IInterceptorFactory.cs deleted file mode 100644 index d3c1e0e..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IInterceptorFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IInterceptorFactory - { - IThingIntercept CreateThingIntercept(); - IPropertyIntercept CreatePropertyIntercept(); - IActionIntercept CreatActionIntercept(); - IEventIntercept CreatEventIntercept(); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IPropertyIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IPropertyIntercept.cs deleted file mode 100644 index 1f45e39..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IPropertyIntercept.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IPropertyIntercept - { - void Before(Thing thing); - void Intercept(Thing thing, PropertyInfo propertyInfo, ThingPropertyAttribute? thingPropertyAttribute); - void After(Thing thing); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IThingIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IThingIntercept.cs deleted file mode 100644 index c09dd7f..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Intercepts/IThingIntercept.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Mozilla.IoT.WebThing.Factories.Generator.Intercepts -{ - public interface IThingIntercept : IIntercept - { - - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesIntercept.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesIntercept.cs deleted file mode 100644 index b5d553e..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesIntercept.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; -using Mozilla.IoT.WebThing.Mapper; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Properties -{ - internal class PropertiesIntercept : IPropertyIntercept - { - public Dictionary Properties { get; } - private readonly ThingOption _option; - - public PropertiesIntercept(ThingOption option) - { - _option = option ?? throw new ArgumentNullException(nameof(option)); - Properties = option.IgnoreCase ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) - : new Dictionary(); - } - - public void Before(Thing thing) - { - - } - - public void Intercept(Thing thing, PropertyInfo propertyInfo, ThingPropertyAttribute? thingPropertyAttribute) - { - var propertyName = thingPropertyAttribute?.Name ?? propertyInfo.Name; - Properties.Add(_option.PropertyNamingPolicy.ConvertName(propertyName), new Property(GetGetMethod(propertyInfo), - GetSetMethod(propertyInfo, thingPropertyAttribute), - CreateValidator(propertyInfo, thingPropertyAttribute), - CreateMapper(propertyInfo.PropertyType))); - } - - private static Func GetGetMethod(PropertyInfo property) - { - var instance = Expression.Parameter(typeof(object), "instance"); - var instanceCast = property.DeclaringType.IsValueType ? - Expression.Convert(instance, property.DeclaringType) : Expression.TypeAs(instance, property.DeclaringType); - - var call = Expression.Call(instanceCast, property.GetGetMethod()); - var typeAs = Expression.TypeAs(call, typeof(object)); - - return Expression.Lambda>(typeAs, instance).Compile(); - } - - private static Action GetSetMethod(PropertyInfo property, ThingPropertyAttribute? thingPropertyAttribute) - { - if ((thingPropertyAttribute != null && thingPropertyAttribute.IsReadOnly) - || !property.CanWrite || !property.SetMethod.IsPublic) - { - return null; - } - - var instance = Expression.Parameter(typeof(object), "instance"); - var value = Expression.Parameter(typeof(object), "value"); - - // value as T is slightly faster than (T)value, so if it's not a value type, use that - var instanceCast = property.DeclaringType.IsValueType ? - Expression.Convert(instance, property.DeclaringType) : Expression.TypeAs(instance, property.DeclaringType); - - var valueCast = property.PropertyType.IsValueType ? - Expression.Convert(value, property.PropertyType) : Expression.TypeAs(value, property.PropertyType); - - var call = Expression.Call(instanceCast, property.GetSetMethod(), valueCast); - return Expression.Lambda>(call, new[] {instance, value}).Compile(); - } - - private static IPropertyValidator CreateValidator(PropertyInfo propertyInfo, ThingPropertyAttribute? thingPropertyAttribute) - { - return new PropertyValidator( - thingPropertyAttribute?.IsReadOnly ?? !propertyInfo.CanWrite, - propertyInfo.PropertyType.GetUnderlyingType(), thingPropertyAttribute); - } - - private static IJsonMapper CreateMapper(Type type) - { - type = Nullable.GetUnderlyingType(type) ?? type; - if (type == typeof(string)) - { - return StringJsonMapper.Instance; - } - - if(type == typeof(bool)) - { - return BoolJsonMapper.Instance; - } - - if (type == typeof(int)) - { - return IntJsonMapper.Instance; - } - - if (type == typeof(uint)) - { - return UIntJsonMapper.Instance; - } - - if (type == typeof(long)) - { - return LongJsonMapper.Instance; - } - - if (type == typeof(ulong)) - { - return ULongJsonMapper.Instance; - } - - if (type == typeof(short)) - { - return ShortJsonMapper.Instance; - } - - if (type == typeof(ushort)) - { - return UShortJsonMapper.Instance; - } - - if (type == typeof(double)) - { - return DoubleJsonMapper.Instance; - } - - if (type == typeof(float)) - { - return FloatJsonMapper.Instance; - } - - if (type == typeof(byte)) - { - return ByteJsonMapper.Instance; - } - - if (type == typeof(sbyte)) - { - return SByteJsonMapper.Instance; - } - - if (type == typeof(decimal)) - { - return DecimalJsonMapper.Instance; - } - - if (type == typeof(DateTime)) - { - return DateTimeJsonMapper.Instance; - } - - if (type == typeof(DateTimeOffset)) - { - return DateTimeOffsetJsonMapper.Instance; - } - - - throw new Exception(); - } - - public void After(Thing thing) - { - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesInterceptFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesInterceptFactory.cs deleted file mode 100644 index 7b563e2..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Properties/PropertiesInterceptFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Properties -{ - internal class PropertiesInterceptFactory : IInterceptorFactory - { - private readonly Thing _thing; - private readonly PropertiesIntercept _intercept; - - public PropertiesInterceptFactory(Thing thing, ThingOption option) - { - _thing = thing ?? throw new ArgumentNullException(nameof(thing)); - _intercept = new PropertiesIntercept(option); - } - - public IThingIntercept CreateThingIntercept() => new EmptyIntercept(); - - public IPropertyIntercept CreatePropertyIntercept() - => _intercept; - - public IActionIntercept CreatActionIntercept() - => new EmptyIntercept(); - - public IEventIntercept CreatEventIntercept() - => new EmptyIntercept(); - - public IProperties Create() - => new WebThing.Properties(_thing, _intercept.Properties); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/ActionVisitor.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/ActionVisitor.cs deleted file mode 100644 index 7bda848..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/ActionVisitor.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Visitor -{ - internal static class ActionVisitor - { - public static void Visit(IEnumerable intercepts, Thing thing) - { - foreach (var intercept in intercepts) - { - intercept.Before(thing); - } - - var thingType = thing.GetType(); - - var actionsInfo = thingType - .GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(x => !x.IsSpecialName - && x.Name != nameof(Equals) && x.Name != nameof(GetType) - && x.Name != nameof(GetHashCode) && x.Name != nameof(ToString)); - - foreach (var action in actionsInfo) - { - var information = action.GetCustomAttribute(); - if (information != null && information.Ignore) - { - continue; - } - - foreach (var intercept in intercepts) - { - intercept.Intercept(thing, action, information); - } - } - - foreach (var intercept in intercepts) - { - intercept.After(thing); - } - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/EventVisitor.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/EventVisitor.cs deleted file mode 100644 index cf6ac97..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/EventVisitor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Visitor -{ - internal static class EventVisitor - { - public static void Visit(IEnumerable intercepts, Thing thing) - { - var thingType = thing.GetType(); - var events = thingType.GetEvents(BindingFlags.Public | BindingFlags.Instance); - - foreach (var intercept in intercepts) - { - intercept.Before(thing); - } - - foreach (var @event in events) - { - var args = @event.EventHandlerType.GetGenericArguments(); - if (args.Length > 1) - { - continue; - } - - if ((args.Length == 0 && @event.EventHandlerType != typeof(EventHandler)) - || (args.Length == 1 && @event.EventHandlerType != typeof(EventHandler<>).MakeGenericType(args[0]))) - { - continue; - } - - - var information = @event.GetCustomAttribute(); - - if (information != null && information.Ignore) - { - continue; - } - - foreach (var intercept in intercepts) - { - intercept.Visit(thing, @event, information); - } - } - - foreach (var intercept in intercepts) - { - intercept.After(thing); - } - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/PropertiesVisitor.cs b/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/PropertiesVisitor.cs deleted file mode 100644 index b47abeb..0000000 --- a/src/Mozilla.IoT.WebThing/Factories/Generator/Visitor/PropertiesVisitor.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Factories.Generator.Intercepts; - -namespace Mozilla.IoT.WebThing.Factories.Generator.Visitor -{ - internal static class PropertiesVisitor - { - public static void Visit(IEnumerable intercepts, Thing thing) - { - var thingType = thing.GetType(); - var properties = thingType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => !IsThingProperty(x.Name)); - - foreach (var intercept in intercepts) - { - intercept.Before(thing); - } - - foreach (var property in properties) - { - var propertyType = property.PropertyType; - if (!IsAcceptedType(propertyType)) - { - continue; - } - - var information = property.GetCustomAttribute(); - - if (information != null && information.Ignore) - { - continue; - } - - foreach (var intercept in intercepts) - { - intercept.Intercept(thing, property, information); - } - } - - - foreach (var intercept in intercepts) - { - intercept.After(thing); - } - } - - private static bool IsAcceptedType(Type? type) - { - if (type == null) - { - return false; - } - - return type == typeof(string) - || type == typeof(bool) - || type == typeof(int) - || type == typeof(byte) - || type == typeof(short) - || type == typeof(long) - || type == typeof(sbyte) - || type == typeof(uint) - || type == typeof(ulong) - || type == typeof(ushort) - || type == typeof(double) - || type == typeof(float) - || type == typeof(decimal) - || type == typeof(DateTime) - || type == typeof(DateTimeOffset) - || type == typeof(bool?) - || type == typeof(int?) - || type == typeof(byte?) - || type == typeof(short?) - || type == typeof(long?) - || type == typeof(sbyte?) - || type == typeof(uint?) - || type == typeof(ulong?) - || type == typeof(ushort?) - || type == typeof(double?) - || type == typeof(float?) - || type == typeof(decimal?) - || type == typeof(DateTime?) - || type == typeof(DateTimeOffset?); - } - - private static bool IsThingProperty(string name) - => name == nameof(Thing.Context) - || name == nameof(Thing.Name) - || name == nameof(Thing.Description) - || name == nameof(Thing.Title) - || name == nameof(Thing.Type); - } -} diff --git a/src/Mozilla.IoT.WebThing/Factories/IActionParameterFactory.cs b/src/Mozilla.IoT.WebThing/Factories/IActionParameterFactory.cs new file mode 100644 index 0000000..5abde31 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/IActionParameterFactory.cs @@ -0,0 +1,20 @@ +using System; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Builders; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + /// The factory of . + /// + public interface IActionParameterFactory + { + /// + /// Create new instance of . + /// + /// The of parameter. + /// The . + /// Return new instance of . + IActionParameter Create(Type parameterType, Information information); + } +} diff --git a/src/Mozilla.IoT.WebThing/Factories/IPropertyFactory.cs b/src/Mozilla.IoT.WebThing/Factories/IPropertyFactory.cs new file mode 100644 index 0000000..a840366 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/IPropertyFactory.cs @@ -0,0 +1,24 @@ +using System; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Properties; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + /// The factory of . + /// + public interface IPropertyFactory + { + /// + /// Create new instance of . + /// + /// The of property. + /// The . + /// + /// + /// + /// New instance of . + IProperty Create(Type propertyType, Information information, Thing thing, + Action setter, Func getter); + } +} diff --git a/src/Mozilla.IoT.WebThing/Factories/IThingContextFactory.cs b/src/Mozilla.IoT.WebThing/Factories/IThingContextFactory.cs new file mode 100644 index 0000000..9fb86cd --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/IThingContextFactory.cs @@ -0,0 +1,18 @@ +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + /// The factory + /// + public interface IThingContextFactory + { + /// + /// Create new instance of . + /// + /// The . + /// The . + /// The new instance of . + ThingContext Create(Thing thing, ThingOption option); + } +} diff --git a/src/Mozilla.IoT.WebThing/Factories/Imp/ActionParameterFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Imp/ActionParameterFactory.cs new file mode 100644 index 0000000..85c73e2 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/Imp/ActionParameterFactory.cs @@ -0,0 +1,191 @@ +using System; +using System.Linq; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Actions.Parameters.Boolean; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Mozilla.IoT.WebThing.Builders; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + public class ActionParameterFactory : IActionParameterFactory + { + /// + public IActionParameter Create(Type parameterType, Information information) + { + if (parameterType == typeof(bool)) + { + return new ParameterBoolean(information.IsNullable); + } + + if (parameterType == typeof(string)) + { + return new ParameterString(information.IsNullable, + information.MinimumLength, information.MaximumLength, information.Pattern, + information.Enums?.Where(x => x != null).Select(Convert.ToString).ToArray()!); + } + + if (parameterType == typeof(char)) + { + return new ParameterChar(information.IsNullable, + information.Enums?.Where(x => x != null).Select(Convert.ToChar).ToArray()); + } + + if (parameterType.IsEnum) + { + return new ParameterEnum(information.IsNullable, parameterType); + } + + if (parameterType == typeof(Guid)) + { + return new ParameterGuid(information.IsNullable, + information.Enums?.Where(x => x != null).Select(x => Guid.Parse(x.ToString()!)).ToArray()); + } + + if (parameterType == typeof(TimeSpan)) + { + return new ParameterTimeSpan(information.IsNullable, + information.Enums?.Where(x => x != null).Select(x => TimeSpan.Parse(x.ToString()!)).ToArray()); + } + + if (parameterType == typeof(DateTime)) + { + return new ParameterDateTime(information.IsNullable, + information.Enums?.Where(x => x != null).Select(Convert.ToDateTime).ToArray()); + } + + if (parameterType == typeof(DateTimeOffset)) + { + return new ParameterDateTimeOffset(information.IsNullable, + information.Enums?.Where(x => x != null).Select(x => DateTimeOffset.Parse(x.ToString()!)).ToArray()); + } + else + { + var minimum = information.Minimum; + var maximum = information.Maximum; + var multipleOf = information.MultipleOf; + var enums = information.Enums; + + if (information.ExclusiveMinimum.HasValue) + { + minimum = information.ExclusiveMinimum!.Value + 1; + } + + if (information.ExclusiveMaximum.HasValue) + { + maximum = information.ExclusiveMaximum!.Value - 1; + } + + if (parameterType == typeof(byte)) + { + var min = minimum.HasValue ? new byte?(Convert.ToByte(minimum!.Value)) : null; + var max = maximum.HasValue ? new byte?(Convert.ToByte(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf!.Value)) : null; + + return new ParameterByte(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToByte).ToArray()); + } + + if (parameterType == typeof(sbyte)) + { + var min = minimum.HasValue ? new sbyte?(Convert.ToSByte(minimum!.Value)) : null; + var max = maximum.HasValue ? new sbyte?(Convert.ToSByte(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new sbyte?(Convert.ToSByte(multipleOf!.Value)) : null; + + return new ParameterSByte(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToSByte).ToArray()); + } + + if (parameterType == typeof(short)) + { + var min = minimum.HasValue ? new short?(Convert.ToInt16(minimum!.Value)) : null; + var max = maximum.HasValue ? new short?(Convert.ToInt16(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new short?(Convert.ToInt16(multipleOf!.Value)) : null; + + return new ParameterShort(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToInt16).ToArray()); + } + + if (parameterType == typeof(ushort)) + { + var min = minimum.HasValue ? new ushort?(Convert.ToUInt16(minimum!.Value)) : null; + var max = maximum.HasValue ? new ushort?(Convert.ToUInt16(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf!.Value)) : null; + + return new ParameterUShort(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToUInt16).ToArray()); + } + + if (parameterType == typeof(int)) + { + var min = minimum.HasValue ? new int?(Convert.ToInt32(minimum!.Value)) : null; + var max = maximum.HasValue ? new int?(Convert.ToInt32(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new int?(Convert.ToInt32(multipleOf!.Value)) : null; + + return new ParameterInt(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToInt32).ToArray()); + } + + if (parameterType == typeof(uint)) + { + var min = minimum.HasValue ? new uint?(Convert.ToUInt32(minimum!.Value)) : null; + var max = maximum.HasValue ? new uint?(Convert.ToUInt32(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new uint?(Convert.ToUInt32(multipleOf!.Value)) : null; + + return new ParameterUInt(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToUInt32).ToArray()); + } + + if (parameterType == typeof(long)) + { + var min = minimum.HasValue ? new long?(Convert.ToInt64(minimum!.Value)) : null; + var max = maximum.HasValue ? new long?(Convert.ToInt64(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new long?(Convert.ToInt64(multipleOf!.Value)) : null; + + return new ParameterLong(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToInt64).ToArray()); + } + + if (parameterType == typeof(ulong)) + { + var min = minimum.HasValue ? new ulong?(Convert.ToUInt64(minimum!.Value)) : null; + var max = maximum.HasValue ? new ulong?(Convert.ToUInt64(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf!.Value)) : null; + + return new ParameterULong(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToUInt64).ToArray()); + } + + if (parameterType == typeof(float)) + { + var min = minimum.HasValue ? new float?(Convert.ToSingle(minimum!.Value)) : null; + var max = maximum.HasValue ? new float?(Convert.ToSingle(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new float?(Convert.ToSingle(multipleOf!.Value)) : null; + + return new ParameterFloat(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToSingle).ToArray()); + } + + if (parameterType == typeof(double)) + { + var min = minimum.HasValue ? new double?(Convert.ToDouble(minimum!.Value)) : null; + var max = maximum.HasValue ? new double?(Convert.ToDouble(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new double?(Convert.ToDouble(multipleOf!.Value)) : null; + + return new ParameterDouble(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToDouble).ToArray()); + } + else + { + var min = minimum.HasValue ? new decimal?(Convert.ToDecimal(minimum!.Value)) : null; + var max = maximum.HasValue ? new decimal?(Convert.ToDecimal(maximum!.Value)) : null; + var multi = multipleOf.HasValue ? new decimal?(Convert.ToDecimal(multipleOf!.Value)) : null; + + return new ParameterDecimal(information.IsNullable, + min, max, multi, enums?.Where(x => x != null).Select(Convert.ToDecimal).ToArray()); + } + } + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Factories/Imp/PropertyFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Imp/PropertyFactory.cs new file mode 100644 index 0000000..1a956c8 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/Imp/PropertyFactory.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq; +using System.Reflection; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Boolean; +using Mozilla.IoT.WebThing.Properties.Number; +using Mozilla.IoT.WebThing.Properties.String; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + public class PropertyFactory : IPropertyFactory + { + /// + public IProperty Create(Type propertyType, Information information, Thing thing, + Action setter, Func getter) + { + propertyType = propertyType.GetUnderlyingType(); + if(propertyType == typeof(bool)) + { + return new PropertyBoolean(thing, getter, setter, information.IsNullable); + } + + if (propertyType == typeof(string)) + { + return new PropertyString(thing, getter, setter, information.IsNullable, + information.MinimumLength, information.MaximumLength, information.Pattern, + information.Enums?.Where(x => x != null).Select(Convert.ToString).ToArray()!); + } + + if (propertyType == typeof(char)) + { + return new PropertyChar(thing, getter, setter, information.IsNullable, + information.Enums?.Where(x => x != null).Select(Convert.ToChar).ToArray()); + } + + if(propertyType.IsEnum) + { + return new PropertyEnum(thing, getter, setter, information.IsNullable, propertyType); + } + + if (propertyType == typeof(Guid)) + { + return new PropertyGuid(thing, getter, setter, information.IsNullable, + information.Enums?.Where(x => x != null).Select(x=> Guid.Parse(x.ToString()!)).ToArray()); + } + + if (propertyType == typeof(TimeSpan)) + { + return new PropertyTimeSpan(thing, getter, setter, information.IsNullable, + information.Enums?.Where(x => x != null).Select(x=> TimeSpan.Parse(x.ToString()!)).ToArray()); + } + + if (propertyType == typeof(DateTime)) + { + return new PropertyDateTime(thing, getter, setter, information.IsNullable, + information.Enums?.Where(x => x != null).Select(Convert.ToDateTime).ToArray()); + } + + if (propertyType == typeof(DateTimeOffset)) + { + return new PropertyDateTimeOffset(thing, getter, setter, information.IsNullable, + information.Enums?.Where(x => x != null).Select(x => DateTimeOffset.Parse(x.ToString()!)).ToArray()); + } + + var minimum = information.Minimum; + var maximum = information.Maximum; + var multipleOf = information.MultipleOf; + var enums = information.Enums?.Where(x => x != null); + + if(information.ExclusiveMinimum.HasValue) + { + minimum = information.ExclusiveMinimum.Value + 1; + } + + if(information.ExclusiveMaximum.HasValue) + { + maximum = information.ExclusiveMaximum.Value - 1; + } + + + if(propertyType == typeof(byte)) + { + var min = minimum.HasValue ? new byte?(Convert.ToByte(minimum.Value)) : null; + var max = maximum.HasValue ? new byte?(Convert.ToByte(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf.Value)) : null; + + return new PropertyByte(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToByte).ToArray()); + } + + if(propertyType == typeof(sbyte)) + { + var min = minimum.HasValue ? new sbyte?(Convert.ToSByte(minimum.Value)) : null; + var max = maximum.HasValue ? new sbyte?(Convert.ToSByte(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new sbyte?(Convert.ToSByte(multipleOf.Value)) : null; + + return new PropertySByte(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToSByte).ToArray()); + } + + if(propertyType == typeof(short)) + { + var min = minimum.HasValue ? new short?(Convert.ToInt16(minimum.Value)) : null; + var max = maximum.HasValue ? new short?(Convert.ToInt16(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new short?(Convert.ToInt16(multipleOf.Value)) : null; + + return new PropertyShort(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToInt16).ToArray()); + } + + if(propertyType == typeof(ushort)) + { + var min = minimum.HasValue ? new ushort?(Convert.ToUInt16(minimum.Value)) : null; + var max = maximum.HasValue ? new ushort?(Convert.ToUInt16(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf.Value)) : null; + + return new PropertyUShort(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToUInt16).ToArray()); + } + + if(propertyType == typeof(int)) + { + var min = minimum.HasValue ? new int?(Convert.ToInt32(minimum.Value)) : null; + var max = maximum.HasValue ? new int?(Convert.ToInt32(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new int?(Convert.ToInt32(multipleOf.Value)) : null; + + return new PropertyInt(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToInt32).ToArray()); + } + + if(propertyType == typeof(uint)) + { + var min = minimum.HasValue ? new uint?(Convert.ToUInt32(minimum.Value)) : null; + var max = maximum.HasValue ? new uint?(Convert.ToUInt32(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new uint?(Convert.ToUInt32(multipleOf.Value)) : null; + + return new PropertyUInt(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToUInt32).ToArray()); + } + + if(propertyType == typeof(long)) + { + var min = minimum.HasValue ? new long?(Convert.ToInt64(minimum.Value)) : null; + var max = maximum.HasValue ? new long?(Convert.ToInt64(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new long?(Convert.ToInt64(multipleOf.Value)) : null; + + return new PropertyLong(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToInt64).ToArray()); + } + + if(propertyType == typeof(ulong)) + { + var min = minimum.HasValue ? new ulong?(Convert.ToUInt64(minimum.Value)) : null; + var max = maximum.HasValue ? new ulong?(Convert.ToUInt64(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new byte?(Convert.ToByte(multipleOf.Value)) : null; + + return new PropertyULong(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToUInt64).ToArray()); + } + + if(propertyType == typeof(float)) + { + var min = minimum.HasValue ? new float?(Convert.ToSingle(minimum.Value)) : null; + var max = maximum.HasValue ? new float?(Convert.ToSingle(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new float?(Convert.ToSingle(multipleOf.Value)) : null; + + return new PropertyFloat(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToSingle).ToArray()); + } + + if(propertyType == typeof(double)) + { + var min = minimum.HasValue ? new double?(Convert.ToDouble(minimum.Value)) : null; + var max = maximum.HasValue ? new double?(Convert.ToDouble(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new double?(Convert.ToDouble(multipleOf.Value)) : null; + + return new PropertyDouble(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToDouble).ToArray()); + } + else + { + var min = minimum.HasValue ? new decimal?(Convert.ToDecimal(minimum.Value)) : null; + var max = maximum.HasValue ? new decimal?(Convert.ToDecimal(maximum.Value)) : null; + var multi = multipleOf.HasValue ? new decimal?(Convert.ToDecimal(multipleOf.Value)) : null; + + return new PropertyDecimal(thing, getter, setter, information.IsNullable, + min, max, multi, enums?.Select(Convert.ToDecimal).ToArray()); + } + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Factories/Imp/ThingContextFactory.cs b/src/Mozilla.IoT.WebThing/Factories/Imp/ThingContextFactory.cs new file mode 100644 index 0000000..ea77eb6 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Factories/Imp/ThingContextFactory.cs @@ -0,0 +1,243 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using Microsoft.AspNetCore.Mvc; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Factories +{ + /// + public class ThingContextFactory : IThingContextFactory + { + private readonly IThingResponseBuilder _response; + private readonly IActionBuilder _action; + private readonly IEventBuilder _event; + private readonly IPropertyBuilder _property; + + /// + /// Initialize a new instance of . + /// + /// + /// + /// + /// + public ThingContextFactory(IEventBuilder @event, IPropertyBuilder property, + IThingResponseBuilder response, IActionBuilder action) + { + _event = @event; + _property = property; + _response = response; + _action = action; + } + + /// + public ThingContext Create(Thing thing, ThingOption option) + { + var thingType = thing.GetType(); + + _response + .SetThing(thing) + .SetThingOption(option); + + _event + .SetThing(thing) + .SetThingOption(option) + .SetThingType(thingType); + + _property + .SetThing(thing) + .SetThingOption(option); + + _action + .SetThing(thing) + .SetThingOption(option) + .SetThingType(thingType); + + VisitEvent(thingType); + VisitProperty(thingType); + VisitAction(thingType); + + return new ThingContext( + _response.Build(), + _event.Build(), + _action.Build(), + _property.Build()); + } + + private static readonly Type s_eventHandler = typeof(EventHandler); + private static readonly Type s_eventHandlerGeneric = typeof(EventHandler<>); + + private void VisitEvent(Type thingType) + { + var events = thingType.GetEvents(BindingFlags.Public | BindingFlags.Instance); + + foreach (var @event in events) + { + var args = @event.EventHandlerType!.GetGenericArguments(); + if (args.Length > 1) + { + continue; + } + + if ((args.Length == 0 && @event.EventHandlerType != s_eventHandler) + || (args.Length == 1 && @event.EventHandlerType != s_eventHandlerGeneric.MakeGenericType(args[0]))) + { + continue; + } + + var information = @event.GetCustomAttribute(); + + if (information != null && information.Ignore) + { + continue; + } + + _event.Add(@event, information); + _response.Add(@event, information); + } + } + + private void VisitProperty(Type thingType) + { + var properties = thingType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => !IsThingProperty(x.Name)); + + foreach (var property in properties) + { + var propertyType = property.PropertyType; + if (!IsAcceptedType(propertyType)) + { + continue; + } + + var attribute = property.GetCustomAttribute(); + + if (attribute != null && attribute.Ignore) + { + continue; + } + + var propertyName = attribute?.Name ?? property.Name; + var isReadOnly = !property.CanWrite || !property.SetMethod!.IsPublic + || (attribute != null && attribute.IsReadOnly); + var isNullable = propertyType == typeof(string) || property.PropertyType.IsNullable(); + var information = ToInformation(propertyName, isReadOnly, isNullable, attribute, propertyType); + + _property.Add(property, information); + _response.Add(property, attribute, information); + } + + static Information ToInformation(string propertyName, bool isReadOnly, bool isNullable, + ThingPropertyAttribute? attribute, Type propertyType) + { + return new Information(attribute?.MinimumValue, attribute?.MaximumValue, + attribute?.ExclusiveMinimumValue, attribute?.ExclusiveMaximumValue, + attribute?.MultipleOfValue, attribute?.MinimumLengthValue, + attribute?.MaximumLengthValue, attribute?.Pattern, GetEnums(propertyType, attribute?.Enum), + isReadOnly, propertyName, isNullable); + } + + static bool IsThingProperty(string name) + => name == nameof(Thing.Context) + || name == nameof(Thing.Name) + || name == nameof(Thing.Description) + || name == nameof(Thing.Title) + || name == nameof(Thing.Type) + || name == nameof(Thing.ThingContext); + } + + private void VisitAction(Type thingType) + { + var methods = thingType + .GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(x => !x.IsSpecialName + && x.Name != nameof(Equals) && x.Name != nameof(GetType) + && x.Name != nameof(GetHashCode) && x.Name != nameof(ToString)); + + foreach (var method in methods) + { + var methodAttribute = method.GetCustomAttribute(); + if (methodAttribute != null && methodAttribute.Ignore) + { + continue; + } + + _response.Add(method, methodAttribute); + _action.Add(method, methodAttribute); + + foreach (var parameter in method.GetParameters()) + { + if (parameter.ParameterType == typeof(CancellationToken) + || parameter.GetCustomAttribute() != null) + { + continue; + } + + var attribute = parameter.GetCustomAttribute(); + var name = attribute?.Name ?? parameter.Name; + var isNullable = parameter.ParameterType == typeof(string) || parameter.ParameterType.IsNullable(); + var information = ToInformation(name!, isNullable, attribute, parameter.ParameterType); + + _action.Add(parameter, information); + _response.Add(parameter, attribute, information); + } + } + + static Information ToInformation(string propertyName, bool isNullable, + ThingParameterAttribute? attribute, Type propertyType) + { + return new Information(attribute?.MinimumValue, attribute?.MaximumValue, + attribute?.ExclusiveMinimumValue, attribute?.ExclusiveMaximumValue, + attribute?.MultipleOfValue, attribute?.MinimumLengthValue, + attribute?.MaximumLengthValue, attribute?.Pattern, GetEnums(propertyType, attribute?.Enum), + false, propertyName, isNullable); + } + } + + private static object[]? GetEnums(Type type, object[]? values) + { + if (type.IsEnum) + { + var enumValues = type.GetEnumNames(); + var result = new object[enumValues.Length]; + Array.Copy(enumValues, 0, result, 0, result.Length); + return result; + } + + return values; + } + + private static bool IsAcceptedType(Type? type) + { + if (type == null) + { + return false; + } + + type = type.GetUnderlyingType(); + + return type == typeof(string) + || type == typeof(char) + || type == typeof(bool) + || type == typeof(int) + || type == typeof(byte) + || type == typeof(short) + || type == typeof(long) + || type == typeof(sbyte) + || type == typeof(uint) + || type == typeof(ulong) + || type == typeof(ushort) + || type == typeof(double) + || type == typeof(float) + || type == typeof(decimal) + || type == typeof(DateTime) + || type == typeof(DateTimeOffset) + || type == typeof(Guid) + || type == typeof(TimeSpan) + || type.IsEnum; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/IProperties.cs b/src/Mozilla.IoT.WebThing/IProperties.cs deleted file mode 100644 index 2d25b57..0000000 --- a/src/Mozilla.IoT.WebThing/IProperties.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace Mozilla.IoT.WebThing -{ - public interface IProperties - { - IEnumerable PropertiesNames { get; } - Dictionary? GetProperties(string? propertyName = null); - - SetPropertyResult SetProperty(string propertyName, object value); - } -} diff --git a/src/Mozilla.IoT.WebThing/IPropertyValidator.cs b/src/Mozilla.IoT.WebThing/IPropertyValidator.cs deleted file mode 100644 index 293005d..0000000 --- a/src/Mozilla.IoT.WebThing/IPropertyValidator.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mozilla.IoT.WebThing -{ - public interface IPropertyValidator - { - bool IsReadOnly { get; } - bool IsValid(object value); - } -} diff --git a/src/Mozilla.IoT.WebThing/Link.cs b/src/Mozilla.IoT.WebThing/Link.cs new file mode 100644 index 0000000..e0dc1cf --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Link.cs @@ -0,0 +1,29 @@ +namespace Mozilla.IoT.WebThing +{ + /// + /// + /// + public readonly struct Link + { + /// + /// + /// + /// + /// + public Link(string href, string? rel) + { + Href = href; + Rel = rel; + } + + /// + /// Representation of a URL. + /// + public string Href { get; } + + /// + /// Describing a relationship + /// + public string? Rel { get; } + } +} diff --git a/src/Mozilla.IoT.WebThing/Mapper/BoolJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/BoolJsonMapper.cs deleted file mode 100644 index 5d50400..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/BoolJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class BoolJsonMapper : IJsonMapper - { - private static BoolJsonMapper? s_instance; - public static BoolJsonMapper Instance => s_instance ??= new BoolJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetBoolean(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/ByteJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/ByteJsonMapper.cs deleted file mode 100644 index b90898d..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/ByteJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class ByteJsonMapper : IJsonMapper - { - private static ByteJsonMapper? s_instance; - public static ByteJsonMapper Instance => s_instance ??= new ByteJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetByte(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/DateTimeJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/DateTimeJsonMapper.cs deleted file mode 100644 index 84effcd..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/DateTimeJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class DateTimeJsonMapper : IJsonMapper - { - private static DateTimeJsonMapper? s_instance; - public static DateTimeJsonMapper Instance => s_instance ??= new DateTimeJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetDateTime(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/DateTimeOffsetJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/DateTimeOffsetJsonMapper.cs deleted file mode 100644 index ea511e4..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/DateTimeOffsetJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class DateTimeOffsetJsonMapper : IJsonMapper - { - private static DateTimeOffsetJsonMapper? s_instance; - public static DateTimeOffsetJsonMapper Instance => s_instance ??= new DateTimeOffsetJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetDateTimeOffset(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/DecimalJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/DecimalJsonMapper.cs deleted file mode 100644 index 96592a3..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/DecimalJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class DecimalJsonMapper : IJsonMapper - { - private static DecimalJsonMapper? s_instance; - public static DecimalJsonMapper Instance => s_instance ??= new DecimalJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetDecimal(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/DoubleJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/DoubleJsonMapper.cs deleted file mode 100644 index 91f62b1..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/DoubleJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class DoubleJsonMapper : IJsonMapper - { - private static DoubleJsonMapper? s_instance; - public static DoubleJsonMapper Instance => s_instance ??= new DoubleJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetDouble(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/FloatJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/FloatJsonMapper.cs deleted file mode 100644 index 3f5f6f8..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/FloatJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class FloatJsonMapper : IJsonMapper - { - private static FloatJsonMapper? s_instance; - public static FloatJsonMapper Instance => s_instance ??= new FloatJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetSingle(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/IJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/IJsonMapper.cs deleted file mode 100644 index f4d4439..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/IJsonMapper.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Mozilla.IoT.WebThing.Mapper -{ - public interface IJsonMapper - { - object Map(object value); - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/IntJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/IntJsonMapper.cs deleted file mode 100644 index a866a49..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/IntJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class IntJsonMapper : IJsonMapper - { - private static IntJsonMapper? s_instance; - public static IntJsonMapper Instance => s_instance ??= new IntJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetInt32(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/LongJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/LongJsonMapper.cs deleted file mode 100644 index a4bf5be..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/LongJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class LongJsonMapper : IJsonMapper - { - private static LongJsonMapper? s_instance; - public static LongJsonMapper Instance => s_instance ??= new LongJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetInt64(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/SByteJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/SByteJsonMapper.cs deleted file mode 100644 index 2ca232c..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/SByteJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class SByteJsonMapper : IJsonMapper - { - private static SByteJsonMapper? s_instance; - public static SByteJsonMapper Instance => s_instance ??= new SByteJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetSByte(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/ShortJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/ShortJsonMapper.cs deleted file mode 100644 index e1412a7..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/ShortJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class ShortJsonMapper : IJsonMapper - { - private static ShortJsonMapper? s_instance; - public static ShortJsonMapper Instance => s_instance ??= new ShortJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetInt16(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/StringJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/StringJsonMapper.cs deleted file mode 100644 index 230b987..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/StringJsonMapper.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class StringJsonMapper : IJsonMapper - { - private static StringJsonMapper? s_instance; - public static StringJsonMapper Instance => s_instance ??= new StringJsonMapper(); - - public object Map(object value) - => ((JsonElement)value).GetString(); - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/UIntJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/UIntJsonMapper.cs deleted file mode 100644 index 3e0b3b8..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/UIntJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class UIntJsonMapper : IJsonMapper - { - private static UIntJsonMapper? s_instance; - public static UIntJsonMapper Instance => s_instance ??= new UIntJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetUInt32(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/ULongJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/ULongJsonMapper.cs deleted file mode 100644 index 87aacb6..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/ULongJsonMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class ULongJsonMapper : IJsonMapper - { - private static ULongJsonMapper? s_instance; - public static ULongJsonMapper Instance => s_instance ??= new ULongJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetUInt64(); - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Mapper/UShortJsonMapper.cs b/src/Mozilla.IoT.WebThing/Mapper/UShortJsonMapper.cs deleted file mode 100644 index 7953b01..0000000 --- a/src/Mozilla.IoT.WebThing/Mapper/UShortJsonMapper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json; - -namespace Mozilla.IoT.WebThing.Mapper -{ - public class UShortJsonMapper : IJsonMapper - { - private static UShortJsonMapper? s_instance; - public static UShortJsonMapper Instance => s_instance ??= new UShortJsonMapper(); - - public object Map(object value) - { - var element = (JsonElement)value; - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return element.GetUInt16(); - } - - } -} diff --git a/src/Mozilla.IoT.WebThing/Middlewares/ThingAdapterMiddleware.cs b/src/Mozilla.IoT.WebThing/Middlewares/ThingAdapterMiddleware.cs new file mode 100644 index 0000000..ec37b73 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Middlewares/ThingAdapterMiddleware.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Mozilla.IoT.WebThing.Extensions; + +namespace Mozilla.IoT.WebThing.Middlewares +{ + internal class ThingAdapterMiddleware + { + private readonly IEnumerable _things; + private readonly ThingOption _option; + private readonly RequestDelegate _next; + + private static bool s_isIdResolved = false; + private static readonly object s_locker = new object(); + public ThingAdapterMiddleware(RequestDelegate next, IEnumerable things, ThingOption option) + { + _next = next; + _things = things; + _option = option; + } + + public Task Invoke(HttpContext httpContext) + { + if (!s_isIdResolved) + { + lock (s_locker) + { + if (!s_isIdResolved) + { + var value = UriHelper.BuildAbsolute(httpContext.Request.Scheme, httpContext.Request.Host); + + foreach (var thing in _things) + { + var builder = new UriBuilder(value) + { + Path = $"/things/{_option.PropertyNamingPolicy.ConvertName(thing.Name)}" + }; + + if (_option.UseThingAdapterUrl) + { + thing.ThingContext.Response.Add(_option.PropertyNamingPolicy.ConvertName("Id"), thing.Name); + thing.ThingContext.Response.Add(_option.PropertyNamingPolicy.ConvertName("Href"), builder.Path); + thing.ThingContext.Response.Add(_option.PropertyNamingPolicy.ConvertName("base"), builder.Uri.ToString()); + } + else + { + thing.ThingContext.Response.Add(_option.PropertyNamingPolicy.ConvertName("Id"), builder.Uri.ToString()); + } + + builder.Scheme = builder.Scheme == "http" ? "ws" : "wss"; + ((List)thing.ThingContext.Response[_option.PropertyNamingPolicy.ConvertName("Links")])! + .Add(new Link("alternate", builder.Uri.ToString())); + } + s_isIdResolved = true; + } + } + } + + return _next(httpContext); + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Mozilla.IoT.WebThing.csproj b/src/Mozilla.IoT.WebThing/Mozilla.IoT.WebThing.csproj index d102624..570c467 100644 --- a/src/Mozilla.IoT.WebThing/Mozilla.IoT.WebThing.csproj +++ b/src/Mozilla.IoT.WebThing/Mozilla.IoT.WebThing.csproj @@ -1,20 +1,23 @@  - $(ThingAppTargetFrameworks) Library enable Mozilla.IoT.WebThing Implementation of an HTTP Mozilla Web Thing. + + true https://github.com/lillo42/webthing-csharp git + true true snupkg + true - + diff --git a/src/Mozilla.IoT.WebThing/Properties.cs b/src/Mozilla.IoT.WebThing/Properties.cs deleted file mode 100644 index 3e59b1b..0000000 --- a/src/Mozilla.IoT.WebThing/Properties.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Mozilla.IoT.WebThing -{ - internal class Properties : IProperties - { - private readonly Thing _thing; - private readonly Dictionary _properties; - - public Properties(Thing thing, - Dictionary properties) - { - _thing = thing ?? throw new ArgumentNullException(nameof(thing)); - _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - } - - public IEnumerable PropertiesNames => _properties.Keys; - - public Dictionary? GetProperties(string? propertyName = null) - { - if (propertyName == null) - { - return _properties.ToDictionary(getter => getter.Key, - getter => getter.Value.Getter(_thing)); - } - - if (_properties.TryGetValue(propertyName, out var property)) - { - return new Dictionary - { - [propertyName] = property.Getter(_thing) - }; - } - - return null; - } - - public SetPropertyResult SetProperty(string propertyName, object value) - { - if (_properties.TryGetValue(propertyName, out var property)) - { - value = property.Mapper.Map(value); - if (property.Validator.IsReadOnly) - { - return SetPropertyResult.ReadOnly; - } - - if (property.Validator.IsValid(value)) - { - property.Setter(_thing, value); - return SetPropertyResult.Ok; - } - - return SetPropertyResult.InvalidValue; - } - - return SetPropertyResult.NotFound; - } - } -} diff --git a/src/Mozilla.IoT.WebThing/Properties/Boolean/PropertyBoolean.cs b/src/Mozilla.IoT.WebThing/Properties/Boolean/PropertyBoolean.cs new file mode 100644 index 0000000..68b0864 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Boolean/PropertyBoolean.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Boolean +{ + /// + /// Represent property. + /// + public readonly struct PropertyBoolean : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepts null value. + public PropertyBoolean(Thing thing, Func getter, Action setter, bool isNullable) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind == JsonValueKind.True) + { + _setter(_thing, true); + return SetPropertyResult.Ok; + } + + if (element.ValueKind == JsonValueKind.False) + { + _setter(_thing, false); + return SetPropertyResult.Ok; + } + + return SetPropertyResult.InvalidValue; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/IProperty.cs b/src/Mozilla.IoT.WebThing/Properties/IProperty.cs new file mode 100644 index 0000000..b031c38 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/IProperty.cs @@ -0,0 +1,23 @@ +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties +{ + /// + /// Get and Set Property of thing + /// + public interface IProperty + { + /// + /// Get value of thing + /// + /// Value of property thing + object? GetValue(); + + /// + /// Set value of thing + /// + /// Input value, from buffer + /// The > + SetPropertyResult SetValue(JsonElement element); + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyByte.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyByte.cs new file mode 100644 index 0000000..c8173b1 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyByte.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyByte : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly byte? _minimum; + private readonly byte? _maximum; + private readonly byte? _multipleOf; + private readonly byte[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyByte(Thing thing, Func getter, Action setter, + bool isNullable, byte? minimum, byte? maximum, byte? multipleOf, byte[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetByte(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDecimal.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDecimal.cs new file mode 100644 index 0000000..598d281 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDecimal.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyDecimal : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly decimal? _minimum; + private readonly decimal? _maximum; + private readonly decimal? _multipleOf; + private readonly decimal[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyDecimal(Thing thing, Func getter, Action setter, + bool isNullable, decimal? minimum, decimal? maximum, decimal? multipleOf, decimal[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetDecimal(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDouble.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDouble.cs new file mode 100644 index 0000000..b598688 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyDouble.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyDouble : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly double? _minimum; + private readonly double? _maximum; + private readonly double? _multipleOf; + private readonly double[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyDouble(Thing thing, Func getter, Action setter, + bool isNullable, double? minimum, double? maximum, double? multipleOf, double[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetDouble(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyFloat.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyFloat.cs new file mode 100644 index 0000000..61d632c --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyFloat.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyFloat : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly float? _minimum; + private readonly float? _maximum; + private readonly float? _multipleOf; + private readonly float[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyFloat(Thing thing, Func getter, Action setter, + bool isNullable, float? minimum, float? maximum, float? multipleOf, float[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetSingle(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyInt.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyInt.cs new file mode 100644 index 0000000..e226bf5 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyInt.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyInt : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly int? _minimum; + private readonly int? _maximum; + private readonly int? _multipleOf; + private readonly int[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyInt(Thing thing, Func getter, Action setter, + bool isNullable, int? minimum, int? maximum, int? multipleOf, int[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetInt32(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyLong.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyLong.cs new file mode 100644 index 0000000..541c50b --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyLong.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyLong : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly long? _minimum; + private readonly long? _maximum; + private readonly long? _multipleOf; + private readonly long[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values that property could have. + public PropertyLong(Thing thing, Func getter, Action setter, + bool isNullable, long? minimum, long? maximum, long? multipleOf, long[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetInt64(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertySByte.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertySByte.cs new file mode 100644 index 0000000..78ac466 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertySByte.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertySByte : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly sbyte? _minimum; + private readonly sbyte? _maximum; + private readonly sbyte? _multipleOf; + private readonly sbyte[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertySByte(Thing thing, Func getter, Action setter, + bool isNullable, sbyte? minimum, sbyte? maximum, sbyte? multipleOf, sbyte[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetSByte(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyShort.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyShort.cs new file mode 100644 index 0000000..88049ba --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyShort.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyShort : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly short? _minimum; + private readonly short? _maximum; + private readonly short? _multipleOf; + private readonly short[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyShort(Thing thing, Func getter, Action setter, + bool isNullable, short? minimum, short? maximum, short? multipleOf, short[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetInt16(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUInt.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUInt.cs new file mode 100644 index 0000000..50a0183 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUInt.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyUInt : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly uint? _minimum; + private readonly uint? _maximum; + private readonly uint? _multipleOf; + private readonly uint[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyUInt(Thing thing, Func getter, Action setter, + bool isNullable, uint? minimum, uint? maximum, uint? multipleOf, uint[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetUInt32(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyULong.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyULong.cs new file mode 100644 index 0000000..003f6df --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyULong.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyULong : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly ulong? _minimum; + private readonly ulong? _maximum; + private readonly ulong? _multipleOf; + private readonly ulong[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyULong(Thing thing, Func getter, Action setter, + bool isNullable, ulong? minimum, ulong? maximum, ulong? multipleOf, ulong[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetUInt64(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUShort.cs b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUShort.cs new file mode 100644 index 0000000..28983b8 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/Number/PropertyUShort.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.Number +{ + /// + /// Represent property. + /// + public readonly struct PropertyUShort : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly ushort? _minimum; + private readonly ushort? _maximum; + private readonly ushort? _multipleOf; + private readonly ushort[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum value to be assign. + /// The maximum value to be assign. + /// The multiple of value to be assign. + /// The possible values this property could have. + public PropertyUShort(Thing thing, Func getter, Action setter, + bool isNullable, ushort? minimum, ushort? maximum, ushort? multipleOf, ushort[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _multipleOf = multipleOf; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.Number) + { + return SetPropertyResult.InvalidValue; + } + + if(!element.TryGetUInt16(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_minimum.HasValue && value < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_multipleOf.HasValue && value % _multipleOf.Value != 0) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/PropertyReadOnly.cs b/src/Mozilla.IoT.WebThing/Properties/PropertyReadOnly.cs new file mode 100644 index 0000000..e42ccb6 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/PropertyReadOnly.cs @@ -0,0 +1,37 @@ +using System; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties +{ + /// + /// Represent read only property. + /// + public readonly struct PropertyReadOnly : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + public PropertyReadOnly(Thing thing, Func getter) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + /// Always return ReadOnly. + /// + /// + /// Always return ReadOnly. + public SetPropertyResult SetValue(JsonElement element) + => SetPropertyResult.ReadOnly; + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/SetPropertyResult.cs b/src/Mozilla.IoT.WebThing/Properties/SetPropertyResult.cs new file mode 100644 index 0000000..9b73ca7 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/SetPropertyResult.cs @@ -0,0 +1,23 @@ +namespace Mozilla.IoT.WebThing.Properties +{ + /// + /// Result of set property + /// + public enum SetPropertyResult + { + /// + /// Set property is OK. + /// + Ok, + + /// + /// Invalid value to set value. + /// + InvalidValue, + + /// + /// If property is read-only. + /// + ReadOnly + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyChar.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyChar.cs new file mode 100644 index 0000000..341b0ab --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyChar.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public readonly struct PropertyChar : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly char[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The possible values this property could have. + public PropertyChar(Thing thing, Func getter, Action setter, + bool isNullable, char[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + var @string = element.GetString(); + + if (@string.Length != 1) + { + return SetPropertyResult.InvalidValue; + } + + var value = @string[0]; + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTime.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTime.cs new file mode 100644 index 0000000..41c80df --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTime.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public readonly struct PropertyDateTime : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly DateTime[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The possible values this property can have. + public PropertyDateTime(Thing thing, Func getter, Action setter, + bool isNullable, DateTime[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + if (!element.TryGetDateTime(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTimeOffset.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTimeOffset.cs new file mode 100644 index 0000000..5703a1d --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyDateTimeOffset.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public readonly struct PropertyDateTimeOffset : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly DateTimeOffset[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The possible values this property could have. + public PropertyDateTimeOffset(Thing thing, Func getter, Action setter, + bool isNullable, DateTimeOffset[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + if (!element.TryGetDateTimeOffset(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyEnum.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyEnum.cs new file mode 100644 index 0000000..ec141f0 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyEnum.cs @@ -0,0 +1,63 @@ +using System; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public class PropertyEnum : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly Type _enumType; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The enum type. + public PropertyEnum(Thing thing, Func getter, Action setter, + bool isNullable, Type enumType) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + if(!Enum.TryParse(_enumType, element.GetString(), true, out var value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyGuid.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyGuid.cs new file mode 100644 index 0000000..c59e040 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyGuid.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public readonly struct PropertyGuid : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly Guid[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The possible values that property could have. + public PropertyGuid(Thing thing, Func getter, Action setter, + bool isNullable, Guid[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + if (!element.TryGetGuid(out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyString.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyString.cs new file mode 100644 index 0000000..875a7cd --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyString.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + /// + /// Represent property. + /// + public readonly struct PropertyString : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly int? _minimum; + private readonly int? _maximum; + private readonly string[]? _enums; + private readonly Regex? _pattern; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The minimum length of string to be assign. + /// The maximum length of string to be assign. + /// The pattern of string to be assign. + /// The possible values this property could have. + public PropertyString(Thing thing, Func getter, Action setter, + bool isNullable, int? minimum, int? maximum, string? pattern, string[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _minimum = minimum; + _maximum = maximum; + _enums = enums; + _pattern = pattern != null ? new Regex(pattern, RegexOptions.Compiled) : null; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + var value = element.GetString(); + + if (_minimum.HasValue && value.Length < _minimum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_maximum.HasValue && value.Length > _maximum.Value) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_pattern != null && !_pattern.IsMatch(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Properties/String/PropertyTimeSpan.cs b/src/Mozilla.IoT.WebThing/Properties/String/PropertyTimeSpan.cs new file mode 100644 index 0000000..546937f --- /dev/null +++ b/src/Mozilla.IoT.WebThing/Properties/String/PropertyTimeSpan.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Text.Json; + +namespace Mozilla.IoT.WebThing.Properties.String +{ + + /// + /// Represent property. + /// + public readonly struct PropertyTimeSpan : IProperty + { + private readonly Thing _thing; + private readonly Func _getter; + private readonly Action _setter; + + private readonly bool _isNullable; + private readonly TimeSpan[]? _enums; + + /// + /// Initialize a new instance of . + /// + /// The . + /// The method to get property. + /// The method to set property. + /// If property accepted null value. + /// The possible values that property could have. + public PropertyTimeSpan(Thing thing, Func getter, Action setter, + bool isNullable, TimeSpan[]? enums) + { + _thing = thing ?? throw new ArgumentNullException(nameof(thing)); + _getter = getter ?? throw new ArgumentNullException(nameof(getter)); + _setter = setter ?? throw new ArgumentNullException(nameof(setter)); + _isNullable = isNullable; + _enums = enums; + } + + /// + public object? GetValue() + => _getter(_thing); + + /// + public SetPropertyResult SetValue(JsonElement element) + { + if (_isNullable && element.ValueKind == JsonValueKind.Null) + { + _setter(_thing, null); + return SetPropertyResult.Ok; + } + + if (element.ValueKind != JsonValueKind.String) + { + return SetPropertyResult.InvalidValue; + } + + if (!TimeSpan.TryParse(element.GetString(), out var value)) + { + return SetPropertyResult.InvalidValue; + } + + if (_enums != null && _enums.Length > 0 && !_enums.Contains(value)) + { + return SetPropertyResult.InvalidValue; + } + + _setter(_thing, value); + return SetPropertyResult.Ok; + } + } +} diff --git a/src/Mozilla.IoT.WebThing/Property.cs b/src/Mozilla.IoT.WebThing/Property.cs deleted file mode 100644 index 7245142..0000000 --- a/src/Mozilla.IoT.WebThing/Property.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Mozilla.IoT.WebThing.Mapper; - -namespace Mozilla.IoT.WebThing -{ - internal sealed class Property - { - public Property(Func getter, - Action setter, - IPropertyValidator validator, - IJsonMapper mapper) - { - Getter = getter ?? throw new ArgumentNullException(nameof(getter)); - Setter = setter; - Validator = validator ?? throw new ArgumentNullException(nameof(validator)); - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - } - - public Action? Setter { get; } - public Func Getter { get; } - public IPropertyValidator Validator { get; } - public IJsonMapper Mapper { get; } - } -} diff --git a/src/Mozilla.IoT.WebThing/PropertyValidator.cs b/src/Mozilla.IoT.WebThing/PropertyValidator.cs deleted file mode 100644 index 953e852..0000000 --- a/src/Mozilla.IoT.WebThing/PropertyValidator.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing -{ - public class PropertyValidator : IPropertyValidator - { - private enum JsonType - { - String, - Number, - Array, - Bool - } - - private readonly object[]? _enums; - private readonly double? _minimum; - private readonly double? _maximum; - private readonly double? _exclusiveMinimum; - private readonly double? _exclusiveMaximum; - private readonly int? _multipleOf; - private readonly uint? _minimumLength; - private readonly uint? _maximumLength; - private readonly Regex? _patter; - private readonly JsonType _type; - - public PropertyValidator(bool isReadOnly, - Type propertyType, - ThingPropertyAttribute? propertyAttribute) - { - IsReadOnly = isReadOnly; - - if (propertyAttribute != null) - { - _enums = propertyAttribute.Enum; - - _minimum = propertyAttribute.MinimumValue; - _maximum = propertyAttribute.MaximumValue; - _multipleOf = propertyAttribute.MultipleOfValue; - - _exclusiveMinimum = propertyAttribute.ExclusiveMinimumValue; - _exclusiveMaximum = propertyAttribute.ExclusiveMaximumValue; - - _minimumLength = propertyAttribute.MinimumLengthValue; - _maximumLength = propertyAttribute.MaximumLengthValue; - _patter = propertyAttribute.Pattern != null - ? new Regex(propertyAttribute.Pattern!, RegexOptions.Compiled) - : null; - } - - if (propertyType == typeof(string)) - { - _type = JsonType.String; - } - else if(propertyType == typeof(bool)) - { - _type = JsonType.Bool; - } - else if(propertyType == typeof(byte) - || propertyType == typeof(sbyte) - || propertyType == typeof(short) - || propertyType == typeof(ushort) - || propertyType == typeof(int) - || propertyType == typeof(uint) - || propertyType == typeof(long) - || propertyType == typeof(ulong) - || propertyType == typeof(float) - || propertyType == typeof(double) - || propertyType == typeof(decimal)) - { - _type = JsonType.Number; - - _enums = _enums?.Select(x => - { - if (x == null) - { - return (object)null; - } - return Convert.ToDouble(x); - }).Distinct().ToArray(); - } - } - - public bool IsReadOnly { get; } - - public bool IsValid(object? value) - { - if (IsReadOnly) - { - return false; - } - - if (_type == JsonType.Number) - { - if (!IsValidNumber(value)) - { - return false; - } - } - - if (_type == JsonType.String) - { - if (!IsValidString(value)) - { - return false; - } - } - - return true; - } - - - private bool IsValidNumber(object value) - { - if (!_minimum.HasValue - && !_maximum.HasValue && !_multipleOf.HasValue - && !_exclusiveMinimum.HasValue - && !_exclusiveMaximum.HasValue - && _enums == null) - { - return true; - } - - var isNull = value == null; - var comparer = Convert.ToDouble(value ?? 0); - if (_minimum.HasValue && (isNull || comparer < _minimum.Value)) - { - return false; - } - - if (_maximum.HasValue && comparer > _maximum.Value) - { - return false; - } - - if (_exclusiveMinimum.HasValue && (isNull || comparer <= _exclusiveMinimum.Value)) - { - return false; - } - - if (_exclusiveMaximum.HasValue && comparer >= _exclusiveMaximum.Value) - { - return false; - } - - if (_multipleOf.HasValue && (isNull || comparer % _multipleOf.Value != 0)) - { - return false; - } - - if (_enums != null && !_enums.Any(x => - { - if (isNull && x == null) - { - return true; - } - - return comparer.Equals(x); - })) - { - return false; - } - - return true; - } - - private bool IsValidString(object value) - { - if (!_minimumLength.HasValue - && !_maximumLength.HasValue - && _patter == null - && _enums == null) - { - return true; - } - - var isNull = value == null; - var comparer = Convert.ToString(value ?? string.Empty); - if (_minimumLength.HasValue && (isNull || comparer.Length < _minimumLength.Value)) - { - return false; - } - - if (_maximumLength.HasValue && comparer.Length > _maximumLength.Value) - { - return false; - } - - if (_patter != null && !_patter.Match(comparer).Success) - { - return false; - } - - if (_enums != null && !_enums.Any(x => - { - if (isNull && x == null) - { - return true; - } - - return comparer.Equals(x); - })) - { - return false; - } - - return true; - } - } -} diff --git a/src/Mozilla.IoT.WebThing/SetPropertyResult.cs b/src/Mozilla.IoT.WebThing/SetPropertyResult.cs deleted file mode 100644 index 11a5d22..0000000 --- a/src/Mozilla.IoT.WebThing/SetPropertyResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mozilla.IoT.WebThing -{ - public enum SetPropertyResult - { - Ok, - NotFound, - InvalidValue, - ReadOnly - } -} diff --git a/src/Mozilla.IoT.WebThing/Thing.cs b/src/Mozilla.IoT.WebThing/Thing.cs index b434604..db70f4d 100644 --- a/src/Mozilla.IoT.WebThing/Thing.cs +++ b/src/Mozilla.IoT.WebThing/Thing.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; using Mozilla.IoT.WebThing.Attributes; using static Mozilla.IoT.WebThing.Const; @@ -12,11 +13,13 @@ namespace Mozilla.IoT.WebThing public abstract class Thing : INotifyPropertyChanged, IEquatable { #region Properties - - internal Uri Prefix { get; set; } = default!; - + + /// + /// Context of Property, Event and Action of thing + /// [ThingProperty(Ignore = true)] - public Context ThingContext { get; set; } = default!; + [JsonIgnore] + public ThingContext ThingContext { get; set; } = default!; /// /// URI for a schema repository which defines standard schemas for common "types" of device capabilities. @@ -48,6 +51,11 @@ public abstract class Thing : INotifyPropertyChanged, IEquatable #endregion + /// + /// Determine the specified object is equal to current object. + /// + /// The to comparer with current object. + /// A indicating if the passed in object obj is Equal to this. public bool Equals(Thing other) { if (ReferenceEquals(null, other)) @@ -65,6 +73,11 @@ public bool Equals(Thing other) && Description == other.Description; } + /// + /// Determine whatever the specified object is equal to current object. + /// + /// The to comparer with current object. + /// A indicating if the passed in object obj is Equal to this. public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -85,14 +98,23 @@ public override bool Equals(object? obj) return Equals((Thing) obj); } + /// + /// Get Hashcode. + /// + /// HashCode public override int GetHashCode() => HashCode.Combine(Context, Title, Description); - public event PropertyChangedEventHandler PropertyChanged; + /// + /// When Property Change. + /// + public event PropertyChangedEventHandler? PropertyChanged; - protected virtual void OnPropertyChanged([CallerMemberName]string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + /// + /// Invoke event + /// + /// Name of Property that has changed. + protected virtual void OnPropertyChanged([CallerMemberName]string? propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } diff --git a/src/Mozilla.IoT.WebThing/ThingContext.cs b/src/Mozilla.IoT.WebThing/ThingContext.cs new file mode 100644 index 0000000..4762857 --- /dev/null +++ b/src/Mozilla.IoT.WebThing/ThingContext.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.WebSockets; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Converts; +using Mozilla.IoT.WebThing.Events; +using Mozilla.IoT.WebThing.Properties; + +namespace Mozilla.IoT.WebThing +{ + /// + /// Represent property, event and action the thing have. + /// This class is used to avoid reflection. + /// + public class ThingContext + { + /// + /// Initialize a new instance of . + /// + /// + /// The with events associated with thing. + /// The with actions associated with thing. + /// The with properties associated with thing. + public ThingContext(Dictionary response, + Dictionary events, + Dictionary actions, + Dictionary properties) + { + Response = response ?? throw new ArgumentNullException(nameof(response)); + Events = events ?? throw new ArgumentNullException(nameof(events)); + Actions = actions ?? throw new ArgumentNullException(nameof(actions)); + Properties = properties ?? throw new ArgumentNullException(nameof(properties)); + } + + /// + /// The Response. + /// + public Dictionary Response { get; } + + /// + /// The properties associated with thing. + /// + public Dictionary Properties { get; } + + /// + /// The events associated with thing. + /// + public Dictionary Events { get; } + + /// + /// The actions associated with thing. + /// + public Dictionary Actions { get; } + + /// + /// The web sockets associated with thing. + /// + public ConcurrentDictionary Sockets { get; } = new ConcurrentDictionary(); + } +} diff --git a/src/Mozilla.IoT.WebThing/WebSockets/AddEventSubscription.cs b/src/Mozilla.IoT.WebThing/WebSockets/AddEventSubscription.cs index 65b0ece..49b0782 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/AddEventSubscription.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/AddEventSubscription.cs @@ -1,29 +1,41 @@ using System; +using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Mozilla.IoT.WebThing.WebSockets { + /// + /// Add event subscription. + /// public class AddEventSubscription : IWebSocketAction { + /// public string Action => "addEventSubscription"; - - public Task ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, - JsonSerializerOptions options, + + /// + public ValueTask ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, IServiceProvider provider, CancellationToken cancellationToken) { var observer = provider.GetRequiredService(); - foreach (var (@event, collection) in thing.ThingContext.Events) + var logger = provider.GetRequiredService>(); + + foreach (var eventName in data.EnumerateObject().TakeWhile(eventName => !cancellationToken.IsCancellationRequested)) { - if (data.TryGetProperty(@event, out _)) + if (thing.ThingContext.Events.TryGetValue(eventName.Name, out var @events)) + { + events.Added += observer.OnEvenAdded; + } + else { - collection.Added += observer.OnEvenAdded; + logger.LogInformation("{eventName} event not found. [Thing: {thing}]", eventName.Name, thing.Name); } } - return Task.CompletedTask; + return new ValueTask(); } } } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/IWebSocketAction.cs b/src/Mozilla.IoT.WebThing/WebSockets/IWebSocketAction.cs index 5d8745f..83e3a7f 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/IWebSocketAction.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/IWebSocketAction.cs @@ -5,11 +5,26 @@ namespace Mozilla.IoT.WebThing.WebSockets { + /// + /// Web socket action. + /// public interface IWebSocketAction { + /// + /// The Action name. This value should be unique. + /// string Action { get; } - Task ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, - JsonSerializerOptions options, IServiceProvider provider, CancellationToken cancellationToken); + /// + /// Execute this action when web socket request action where action name match with + /// + /// The origin of this action. + /// The associated with action. + /// The request with this action. + /// The for this action. Every request is generate new scope. + /// The . + /// + ValueTask ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, + IServiceProvider provider, CancellationToken cancellationToken); } } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/RequestAction.cs b/src/Mozilla.IoT.WebThing/WebSockets/RequestAction.cs index bf66bd6..b34765d 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/RequestAction.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/RequestAction.cs @@ -4,56 +4,64 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Actions; namespace Mozilla.IoT.WebThing.WebSockets { + /// + /// Execute request action + /// public class RequestAction : IWebSocketAction { private static readonly ArraySegment s_errorMessage = new ArraySegment(Encoding.UTF8.GetBytes(@"{""messageType"": ""error"",""data"": {""status"": ""400 Bad Request"",""message"": ""Invalid action request""}}")); private readonly ILogger _logger; + /// + /// Initialize a new instance of . + /// + /// public RequestAction(ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + /// public string Action => "requestAction"; - public Task ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, JsonSerializerOptions options, + /// + public ValueTask ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, IServiceProvider provider, CancellationToken cancellationToken) { - foreach (var (actionName, actionContext) in thing.ThingContext.Actions) + var option = provider.GetRequiredService(); + foreach (var property in data.EnumerateObject()) { - if(!data.TryGetProperty(actionName, out var json)) + if (!thing.ThingContext.Actions.TryGetValue(property.Name, out var actions)) { continue; } - - _logger.LogTrace("{actionName} Action found. [Name: {thingName}]", actionName, thing.Name); - var actionInfo = (ActionInfo)JsonSerializer.Deserialize(json.GetRawText(), actionContext.ActionType, options); - - if (!actionInfo.IsValid()) + + if (!actions.TryAdd(property.Value, out var action)) { - _logger.LogInformation("{actionName} Action has invalid parameters. [Name: {thingName}]", actionName, thing.Name); + _logger.LogInformation("{actionName} Action has invalid parameters. [Name: {thingName}]", property.Name, thing.Name); socket.SendAsync(s_errorMessage, WebSocketMessageType.Text, true, cancellationToken) .ConfigureAwait(false); continue; } + + action.Thing = thing; - _logger.LogInformation("Going to execute {actionName} action. [Name: {thingName}]", actionName, thing.Name); + _logger.LogInformation("Going to execute {actionName} action. [Name: {thingName}]", action.GetActionName(), thing.Name); - var namePolicy = options.PropertyNamingPolicy; - actionInfo.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(actionName)}/{actionInfo.Id}"; - - thing.ThingContext.Actions[actionInfo.GetActionName()].Actions.Add(actionInfo.Id, actionInfo); - actionInfo.ExecuteAsync(thing, provider) + var namePolicy = option.PropertyNamingPolicy; + action.Href = $"/things/{namePolicy.ConvertName(thing.Name)}/actions/{namePolicy.ConvertName(action.GetActionName())}/{action.GetId()}"; + + action.ExecuteAsync(thing, provider) .ConfigureAwait(false); } - return Task.CompletedTask; + return new ValueTask(); } } } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/SetThingProperty.cs b/src/Mozilla.IoT.WebThing/WebSockets/SetThingProperty.cs index da5a30e..a2359c4 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/SetThingProperty.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/SetThingProperty.cs @@ -3,59 +3,87 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing.Properties; namespace Mozilla.IoT.WebThing.WebSockets { + /// + /// Set property value action. + /// public class SetThingProperty : IWebSocketAction { private readonly ILogger _logger; + /// + /// Initialize a new instance of . + /// + /// public SetThingProperty(ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + /// public string Action => "setProperty"; - public Task ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, JsonSerializerOptions options, + /// + public ValueTask ExecuteAsync(System.Net.WebSockets.WebSocket socket, Thing thing, JsonElement data, IServiceProvider provider, CancellationToken cancellationToken) { - foreach (var propertyName in thing.ThingContext.Properties.PropertiesNames) - { - if (!data.TryGetProperty(options.PropertyNamingPolicy.ConvertName(propertyName), out var property)) - { - continue; - } + var option = provider.GetRequiredService(); - var result = thing.ThingContext.Properties.SetProperty(propertyName, property); - if (result == SetPropertyResult.InvalidValue) + foreach (var jsonProperty in data.EnumerateObject()) + { + if (!thing.ThingContext.Properties.TryGetValue(jsonProperty.Name, out var property)) { - _logger.LogInformation("Invalid property value. [Thing: {thing}][Property Name: {propertyName}]", thing.Name, propertyName); - + _logger.LogInformation("Property not found. [Thing: {thing}][Property Name: {propertyName}]", thing.Name, jsonProperty.Name); var response = JsonSerializer.SerializeToUtf8Bytes( new WebSocketResponse("error", - new ErrorResponse("400 Bad Request", "Invalid property value")), options); + new ErrorResponse("404 Not found", "Property not found")), option); socket.SendAsync(response, WebSocketMessageType.Text, true, cancellationToken) .ConfigureAwait(false); } - if (result == SetPropertyResult.ReadOnly) + switch (property!.SetValue(jsonProperty.Value)) { - _logger.LogInformation("Read-only property. [Thing: {thing}][Property Name: {propertyName}]", thing.Name, propertyName); - - var response = JsonSerializer.SerializeToUtf8Bytes( - new WebSocketResponse("error", - new ErrorResponse("400 Bad Request", "Read-only property")), options); + case SetPropertyResult.InvalidValue: + { + _logger.LogInformation( + "Invalid property value. [Thing: {thing}][Property Name: {propertyName}]", + thing.Name, jsonProperty.Name); - socket.SendAsync(response, WebSocketMessageType.Text, true, cancellationToken) - .ConfigureAwait(false); - + var response = JsonSerializer.SerializeToUtf8Bytes( + new WebSocketResponse("error", + new ErrorResponse("400 Bad Request", "Invalid property value")), option); + + socket.SendAsync(response, WebSocketMessageType.Text, true, cancellationToken) + .ConfigureAwait(false); + break; + } + case SetPropertyResult.ReadOnly: + { + _logger.LogInformation("Read-only property. [Thing: {thing}][Property Name: {propertyName}]", + thing.Name, jsonProperty.Name); + + var response = JsonSerializer.SerializeToUtf8Bytes( + new WebSocketResponse("error", + new ErrorResponse("400 Bad Request", "Read-only property")), option); + + socket.SendAsync(response, WebSocketMessageType.Text, true, cancellationToken) + .ConfigureAwait(false); + break; + } + case SetPropertyResult.Ok: + break; + default: + throw new ArgumentOutOfRangeException(); } } - - return Task.CompletedTask; + + return new ValueTask(); } } } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/ThingObserver.cs b/src/Mozilla.IoT.WebThing/WebSockets/ThingObserver.cs index abc5e31..1856902 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/ThingObserver.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/ThingObserver.cs @@ -6,17 +6,19 @@ using System.Threading; using Microsoft.Extensions.Logging; using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Events; namespace Mozilla.IoT.WebThing.WebSockets { - public class ThingObserver + + internal class ThingObserver { private readonly ILogger _logger; private readonly Thing _thing; private readonly JsonSerializerOptions _options; private readonly System.Net.WebSockets.WebSocket _socket; private readonly CancellationToken _cancellation; - + public ThingObserver(ILogger logger, JsonSerializerOptions options, System.Net.WebSockets.WebSocket socket, @@ -30,46 +32,56 @@ public ThingObserver(ILogger logger, _thing = thing ?? throw new ArgumentNullException(nameof(thing)); } - public HashSet EventsBind { get; } = new HashSet(); + public IEnumerable EventsBind { get; } = new HashSet(); - public async void OnEvenAdded(object sender, Event @event) + public async void OnEvenAdded(object? sender, Event @event) { + if (sender == null) + { + return; + } + _logger.LogInformation("Event add received, going to notify Web Socket"); var sent = JsonSerializer.SerializeToUtf8Bytes(new WebSocketResponse("event", - new Dictionary + new Dictionary { - [sender.ToString()] = @event + [sender.ToString()!] = @event }), _options); await _socket.SendAsync(sent, WebSocketMessageType.Text, true, _cancellation) .ConfigureAwait(false); } - + public async void OnPropertyChanged(object sender, PropertyChangedEventArgs property) { - var data = _thing.ThingContext.Properties.GetProperties(property.PropertyName); - _logger.LogInformation("Event add received, going to notify Web Socket"); + var data = _thing.ThingContext.Properties[property.PropertyName]; + _logger.LogInformation("Property changed, going to notify via Web Socket. [Property: {propertyName}]", property.PropertyName); var sent = JsonSerializer.SerializeToUtf8Bytes(new WebSocketResponse("propertyStatus", - new Dictionary + new Dictionary { - [_options.GetPropertyName(property.PropertyName)] = data[property.PropertyName] + [_options.GetPropertyName(property.PropertyName)] = data.GetValue() }), _options); await _socket.SendAsync(sent, WebSocketMessageType.Text, true, _cancellation) .ConfigureAwait(false); } - - public async void OnActionChange(object sender, ActionInfo action) + + public async void OnActionChange(object? sender, ActionInfo action) { + if (sender == null) + { + return; + } + + _logger.LogInformation("Action Status changed, going to notify via Web Socket. [Action: {propertyName}][Status: {status}]", action.GetActionName(), action.Status); await _socket.SendAsync( JsonSerializer.SerializeToUtf8Bytes(new WebSocketResponse("actionStatus",new Dictionary { - [ action.GetActionName()] = action + [action.GetActionName()] = action }), _options), WebSocketMessageType.Text, true, _cancellation) .ConfigureAwait(false); } } - } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/WebSocket.cs b/src/Mozilla.IoT.WebThing/WebSockets/WebSocket.cs index 039020f..624fd09 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/WebSocket.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/WebSocket.cs @@ -122,8 +122,8 @@ await socket.SendAsync(s_error, WebSocketMessageType.Text, true, cancellation) } using var scope = service.CreateScope(); - scope.ServiceProvider.GetRequiredService().Observer = observer; - await action.ExecuteAsync(socket, thing, data, jsonOptions, scope.ServiceProvider, cancellation) + scope.ServiceProvider.GetRequiredService().Observer = observer; + await action.ExecuteAsync(socket, thing, data, scope.ServiceProvider, cancellation) .ConfigureAwait(false); messageTypeString = string.Empty; @@ -160,7 +160,7 @@ private static void BindActions(Thing thing, ThingObserver observer) { foreach (var (_, actionContext) in thing.ThingContext.Actions) { - actionContext.Actions.Change += observer.OnActionChange; + actionContext.Change += observer.OnActionChange; } } @@ -171,7 +171,7 @@ private static void UnbindActions(Thing thing, ThingObserver observer) { foreach (var (_, actionContext) in thing.ThingContext.Actions) { - actionContext.Actions.Change -= observer.OnActionChange; + actionContext.Change -= observer.OnActionChange; } } diff --git a/src/Mozilla.IoT.WebThing/WebSockets/WebSocketResponse.cs b/src/Mozilla.IoT.WebThing/WebSockets/WebSocketResponse.cs index b46d2a8..5f36e34 100644 --- a/src/Mozilla.IoT.WebThing/WebSockets/WebSocketResponse.cs +++ b/src/Mozilla.IoT.WebThing/WebSockets/WebSocketResponse.cs @@ -1,25 +1,25 @@ namespace Mozilla.IoT.WebThing.WebSockets { - public class WebSocketResponse + internal class WebSocketResponse { public WebSocketResponse(string messageType, object data) { MessageType = messageType; Data = data; } - + public string MessageType { get; } public object Data { get; } } - public class ErrorResponse + internal class ErrorResponse { public ErrorResponse(string status, string message) { Status = status; Message = message; } - + public string Status { get; } public string Message { get; } } diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Action.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Action.cs deleted file mode 100644 index 9e3bf35..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Action.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class Action - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - public Action() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - [Theory] - [InlineData(50, 2_000)] - public async Task Create(int level, int duration) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions", - new StringContent($@" -{{ - ""fade"": {{ - ""input"": {{ - ""level"": {level}, - ""duration"": {duration} - }} - }} -}}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Level.Should().Be(level); - json.Input.Duration.Should().Be(duration); - json.Href.Should().StartWith("/things/action/actions/fade/"); - json.Status.Should().NotBeNullOrEmpty(); - json.TimeRequested.Should().BeBefore(DateTime.UtcNow); - } - - [Theory] - [InlineData(50, 2_000)] - public async Task CreateInSpecificUrl(int level, int duration) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions/fade", - new StringContent($@" -{{ - ""fade"": {{ - ""input"": {{ - ""level"": {level}, - ""duration"": {duration} - }} - }} -}}"), source.Token).ConfigureAwait(false); - - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Level.Should().Be(level); - json.Input.Duration.Should().Be(duration); - json.Href.Should().StartWith("/things/action/actions/fade/"); - json.Status.Should().NotBeNullOrEmpty(); - json.TimeRequested.Should().BeBefore(DateTime.UtcNow); - } - - [Fact] - public async Task InvalidAction() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions/aaaa", - new StringContent(@" -{ - ""aaaa"": { - ""input"": { - ""level"": 10, - ""duration"": 100 - } - } -}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } - - [Fact] - public async Task TryCreateActionWithOtherName() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions/fade", - new StringContent(@" -{ - ""aaaa"": { - ""input"": { - ""level"": 10, - ""duration"": 100 - } - } -}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Theory] - [InlineData(-1, 2_000)] - [InlineData(101, 2_000)] - public async Task TryCreateWithInvalidParameter(int level, int duration) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions", - new StringContent($@" -{{ - ""fade"": {{ - ""input"": {{ - ""level"": {level}, - ""duration"": {duration} - }} - }} -}}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - - response = await _client.GetAsync("/things/action/actions", source.Token).ConfigureAwait(false); - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCount(0); - } - - [Fact] - public async Task LongRunner() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions", - new StringContent($@" -{{ - ""longRun"": {{ - }} -}}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Href.Should().StartWith("/things/action/actions/longRun/"); - json.Status.Should().NotBeNullOrEmpty(); - json.TimeRequested.Should().BeBefore(DateTime.UtcNow); - - await Task.Delay(3_000).ConfigureAwait(false); - - response = await _client.GetAsync($"/things/action/actions/longRun/{json.Href.Substring(json.Href.LastIndexOf('/') + 1)}", source.Token) - .ConfigureAwait(false); - - message = await response.Content.ReadAsStringAsync(); - json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Href.Should().StartWith("/things/action/actions/longRun/"); - json.Status.Should().NotBeNullOrEmpty(); - json.Status.Should().Be("completed"); - json.TimeRequested.Should().BeBefore(DateTime.UtcNow); - json.TimeCompleted.Should().NotBeNull(); - json.TimeCompleted.Should().BeBefore(DateTime.UtcNow); - } - - [Fact] - public async Task CancelAction() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action/actions", - new StringContent($@" -{{ - ""LongRun"": {{ - }} -}}"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Href.Should().StartWith("/things/action/actions/longRun/"); - json.Status.Should().NotBeNullOrEmpty(); - json.TimeRequested.Should().BeBefore(DateTime.UtcNow); - - response = await _client.DeleteAsync($"/things/action/actions/longRun/{json.Href.Substring(json.Href.LastIndexOf('/') + 1)}", source.Token) - .ConfigureAwait(false); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); - - response = await _client.GetAsync($"/things/action/actions/longRun/{json.Href.Substring(json.Href.LastIndexOf('/') + 1)}", source.Token) - .ConfigureAwait(false); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } - - public class LongRun - { - public string Href { get; set; } - public string Status { get; set; } - public DateTime TimeRequested { get; set; } - public DateTime? TimeCompleted { get; set; } - } - - public class Fade - { - public Input Input { get; set; } - public string Href { get; set; } - public string Status { get; set; } - public DateTime TimeRequested { get; set; } - public DateTime? TimeCompleted { get; set; } - } - - public class Input - { - public int Level { get; set; } - public int Duration { get; set; } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/ActionType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/ActionType.cs deleted file mode 100644 index 455af49..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/ActionType.cs +++ /dev/null @@ -1,676 +0,0 @@ -using System; -using System.Globalization; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class ActionType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - private readonly Fixture _fixture; - - public ActionType() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - [Fact] - public async Task RunAction() - { - var @bool = _fixture.Create(); - var @byte = _fixture.Create(); - var @sbyte = _fixture.Create(); - var @short = _fixture.Create(); - var @ushort = _fixture.Create(); - var @int = _fixture.Create(); - var @uint = _fixture.Create(); - var @long = _fixture.Create(); - var @ulong = _fixture.Create(); - var @double = _fixture.Create(); - var @float = _fixture.Create(); - var @decimal = _fixture.Create(); - var @string = _fixture.Create(); - var @dateTime = _fixture.Create(); - var @dateTimeOffset = _fixture.Create(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/run", - new StringContent($@" -{{ - ""run"": {{ - ""input"": {{ - ""bool"": {@bool.ToString().ToLower()}, - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal}, - ""string"": ""{@string}"", - ""dateTime"": ""{@dateTime:O}"", - ""dateTimeOffset"": ""{@dateTimeOffset:O}"" - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Bool.Should().Be(@bool); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); - json.Input.Decimal.Should().Be(@decimal); - json.Input.String.Should().Be(@string); - json.Input.DateTime.Should().Be(dateTime); - json.Input.DateTimeOffset.Should().Be(dateTimeOffset); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Fact] - public async Task RunWithValidation() - { - var @byte = (byte)10; - var @sbyte = (sbyte)10; - var @short = (short)10; - var @ushort = (ushort)10; - var @int = 10; - var @uint = (uint)10; - var @long = (long)10; - var @ulong = (ulong)10; - var @double = (double)10; - var @float = (float)10; - var @decimal = (decimal)10; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidation", - new StringContent($@" -{{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); -// json.Input.Decimal.Should().Be(@decimal); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithValidationActionMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)1 : (byte)100; - var @sbyte = isMin ? (sbyte)1 : (sbyte)100; - var @short = isMin ? (short)1 : (short)100; - var @ushort = isMin ? (ushort)1 : (ushort)100; - var @int = isMin ? (int)1 : 100; - var @uint = isMin ? 1 : (uint)100; - var @long = isMin ? 1 : (long)100; - var @ulong = isMin ? 1 : (ulong)100; - var @double = isMin ? 1 : (double)100; - var @float = isMin ? 1 : (float)100; - var @decimal = isMin ? 1 : (decimal)100; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidation", - new StringContent($@" -{{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); -// json.Input.Decimal.Should().Be(@decimal); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithInvalidation(bool isMin) - { - var @byte = isMin ? (byte)0 : (byte)101; - var @sbyte = isMin ? (sbyte)0 : (sbyte)101; - var @short = isMin ? (short)0 : (short)101; - var @ushort = isMin ? (ushort)0 : (ushort)101; - var @int = isMin ? 0 : 101; - var @uint = isMin ? 0 : (uint)101; - var @long = isMin ? 0 : (long)101; - var @ulong = isMin ? 0 : (ulong)101; - var @double = isMin ? 0 : (double)101; - var @float = isMin ? 0 : (float)101; - var @decimal = isMin ? 0 : (decimal)101; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidation", - new StringContent($@" -{{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Fact] - public async Task RunWithValidationExclusiveValid() - { - var @byte = (byte)10; - var @sbyte = (sbyte)10; - var @short = (short)10; - var @ushort = (ushort)10; - var @int = 10; - var @uint = (uint)10; - var @long = (long)10; - var @ulong = (ulong)10; - var @double = (double)10; - var @float = (float)10; - var @decimal = (decimal)10; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidationExclusive", - new StringContent($@" -{{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); -// json.Input.Decimal.Should().Be(@decimal); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithValidationExclusiveMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)2 : (byte)99; - var @sbyte = isMin ? (sbyte)2 : (sbyte)99; - var @short = isMin ? (short)2 : (short)99; - var @ushort = isMin ? (ushort)2 : (ushort)99; - var @int = isMin ? 2 : 99; - var @uint = isMin ? 2 : (uint)99; - var @long = isMin ? 2 : (long)99; - var @ulong = isMin ? 2 : (ulong)99; - var @double = isMin ? 2 : (double)99; - var @float = isMin ? 2 : (float)99; - var @decimal = isMin ? 2 : (decimal)99; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidationExclusive", - new StringContent($@" -{{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); -// json.Input.Decimal.Should().Be(@decimal); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithValidationExclusiveActionInvalid(bool isMin) - { - var @byte = isMin ? 1 : 100; - var @sbyte = isMin ? 1 : 100; - var @short = isMin ? 1 : 100; - var @ushort = isMin ? 1 : 100; - var @int = isMin ? 1 : 100; - var @uint =isMin ? 1 : 100; - var @long = isMin ? 1 : 100; - var @ulong = isMin ? 1 : 100; - var @double = isMin ? 1 : 100; - var @float = isMin ? 1 : 100; - var @decimal = isMin ? 1 : 100; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithValidationExclusive", - new StringContent($@" -{{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunNullAction(bool isNull) - { - var @bool = isNull ? null : new bool?(_fixture.Create()); - var @byte = isNull ? null : new byte?(_fixture.Create()); - var @sbyte = isNull ? null : new sbyte?(_fixture.Create()); - var @short = isNull ? null : new short?(_fixture.Create()); - var @ushort = isNull ? null : new ushort?(_fixture.Create()); - var @int = isNull ? null : new int?(_fixture.Create()); - var @uint = isNull ? null : new uint?(_fixture.Create()); - var @long = isNull ? null : new long?(_fixture.Create()); - var @ulong = isNull ? null : new ulong?(_fixture.Create()); - var @double = isNull ? null : new double?(_fixture.Create()); - var @float = isNull ? null : new float?(_fixture.Create()); - var @decimal = isNull ? null : new decimal?(_fixture.Create()); - var @string = isNull ? null : _fixture.Create(); - var @dateTime = isNull ? null : new DateTime?(_fixture.Create()); - var @dateTimeOffset = isNull ? null : new DateTimeOffset?(_fixture.Create()); - - var @boolS = isNull ? "null" : @bool.ToString().ToLower(); - var @byteS = isNull ? "null" : @byte.ToString(); - var @sbyteS = isNull ? "null" : @sbyte.ToString(); - var @shortS = isNull ? "null" : @short.ToString(); - var @ushortS = isNull ? "null" : @ushort.ToString(); - var @intS = isNull ? "null" : @int.ToString(); - var @uintS = isNull ? "null" : @uint.ToString(); - var @longS = isNull ? "null" : @long.ToString(); - var @ulongS = isNull ? "null" : @ulong.ToString(); - var @doubleS = isNull ? "null" : @double.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @floatS = isNull ? "null" : @float.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @decimalS = isNull ? "null" : @decimal.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @stringS = isNull ? "null" : $"\"{@string}\""; - var @dateTimeS = isNull ? "null" : $"\"{@dateTime:O}\""; - var @dateTimeOffsetS = isNull ? "null" : $"\"{@dateTimeOffset:O}\""; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runNull", - new StringContent($@" -{{ - ""runNull"": {{ - ""input"": {{ - ""bool"": {@boolS}, - ""byte"": {@byteS}, - ""sbyte"": {@sbyteS}, - ""short"": {@shortS}, - ""ushort"": {@ushortS}, - ""int"": {@intS}, - ""uint"": {@uintS}, - ""long"": {@longS}, - ""ulong"": {@ulongS}, - ""double"": {@doubleS}, - ""float"": {@floatS}, - ""decimal"": {@decimalS}, - ""string"": {@stringS}, - ""dateTime"": {@dateTimeS}, - ""dateTimeOffset"": {@dateTimeOffsetS} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Bool.Should().Be(@bool); - json.Input.Byte.Should().Be(@byte); - json.Input.Sbyte.Should().Be(@sbyte); - json.Input.Short.Should().Be(@short); - json.Input.UShort.Should().Be(@ushort); - json.Input.Int.Should().Be(@int); - json.Input.Uint.Should().Be(@uint); - json.Input.Long.Should().Be(@long); - json.Input.ULong.Should().Be(@ulong); - json.Input.Double.Should().Be(@double); - json.Input.Float.Should().Be(@float); - json.Input.Decimal.Should().Be(@decimal); - json.Input.String.Should().Be(@string); - json.Input.DateTime.Should().Be(dateTime); - json.Input.DateTimeOffset.Should().Be(dateTimeOffset); - json.Status.Should().NotBeNullOrEmpty(); - } - - public class Run - { - public Input Input { get; set; } - public string Href { get; set; } - public string Status { get; set; } - public DateTime TimeRequested { get; set; } - public DateTime? TimeCompleted { get; set; } - } - - [Theory] - [InlineData("a")] - [InlineData("abc")] - [InlineData("0123456789")] - public async Task RunStringValidation(string min) - { - var email = "test@gmail.com"; - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithStringValidation", - new StringContent($@" -{{ - ""runWithStringValidation"": {{ - ""input"": {{ - ""minAnMax"": ""{min}"", - ""mail"": ""{email}"" - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json.Input.Should().NotBeNull(); - json.Input.Mail.Should().Be(email); - json.Input.MinAnMax.Should().Be(min); - json.Status.Should().NotBeNullOrEmpty(); - } - - [Theory] - [InlineData(null, "test@tese.com")] - [InlineData("", "test@tese.com")] - [InlineData("a0123456789", "test@tese.com")] - [InlineData("abc", null)] - [InlineData("abc", "test")] - public async Task RunStringInvalidation(string min, string email) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/action-type/actions/runWithStringValidation", - new StringContent($@" -{{ - ""runWithStringValidation"": {{ - ""input"": {{ - ""minAnMax"": ""{min}"", - ""mail"": ""{email}"" - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - public class Input - { - public bool Bool { get; set; } - public byte Byte { get; set; } - public sbyte Sbyte { get; set; } - public short Short { get; set; } - public ushort UShort { get; set; } - public int Int { get; set; } - public uint Uint { get; set; } - public long Long { get; set; } - public ulong ULong { get; set; } - public double Double { get; set; } - public float Float { get; set; } - public decimal Decimal { get; set; } - public string String { get; set; } - public DateTime DateTime { get; set; } - public DateTimeOffset DateTimeOffset { get; set; } - - } - - public class RunNull - { - public InputNull Input { get; set; } - public string Href { get; set; } - public string Status { get; set; } - public DateTime TimeRequested { get; set; } - public DateTime? TimeCompleted { get; set; } - } - - public class InputNull - { - public bool? Bool { get; set; } - public byte? Byte { get; set; } - public sbyte? Sbyte { get; set; } - public short? Short { get; set; } - public ushort? UShort { get; set; } - public int? Int { get; set; } - public uint? Uint { get; set; } - public long? Long { get; set; } - public ulong? ULong { get; set; } - public double? Double { get; set; } - public float? Float { get; set; } - public decimal? Decimal { get; set; } - public string? String { get; set; } - public DateTime? DateTime { get; set; } - public DateTimeOffset? DateTimeOffset { get; set; } - } - public class RunString - { - public InputString Input { get; set; } - public string Href { get; set; } - public string Status { get; set; } - public DateTime TimeRequested { get; set; } - public DateTime? TimeCompleted { get; set; } - } - public class InputString - { - public string? MinAnMax { get; set; } - public string? Mail { get; set; } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/EventType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/EventType.cs deleted file mode 100644 index 605c8e2..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/EventType.cs +++ /dev/null @@ -1,343 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class EventType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - private readonly Fixture _fixture; - - public EventType() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - [Fact] - public async Task GetNoNullable() - { - var @bool = _fixture.Create(); - var @byte = _fixture.Create(); - var @sbyte = _fixture.Create(); - var @short = _fixture.Create(); - var @ushort = _fixture.Create(); - var @int = _fixture.Create(); - var @uint = _fixture.Create(); - var @long = _fixture.Create(); - var @ulong = _fixture.Create(); - var @double = _fixture.Create(); - var @float = _fixture.Create(); - var @decimal = _fixture.Create(); - var @string = _fixture.Create(); - var @dateTime = _fixture.Create(); - var @dateTimeOffset = _fixture.Create(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/event-type/actions/run", - new StringContent($@" -{{ - ""run"": {{ - ""input"": {{ - ""bool"": {@bool.ToString().ToLower()}, - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal}, - ""string"": ""{@string}"", - ""dateTime"": ""{@dateTime:O}"", - ""dateTimeOffset"": ""{@dateTimeOffset:O}"" - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - response = await _client.GetAsync("/things/event-type/events", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be("application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject>(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - var validBool = json.Where(x => x.Bool != null).Select(x => x.Bool).ToArray(); - validBool.Should().HaveCountGreaterOrEqualTo(1); - validBool.Should().Contain(x => x.Data == @bool); - - var validByte = json.Where(x => x.Byte != null).Select(x=> x.Byte).ToArray(); - validByte.Should().HaveCountGreaterOrEqualTo(1); - validByte.Should().Contain(x => x.Data == @byte); - - var validSByte = json.Where(x => x.Sbyte != null).Select(x=> x.Sbyte).ToArray(); - validSByte.Should().HaveCountGreaterOrEqualTo(1); - validSByte.Should().Contain(x => x.Data == @sbyte); - - var validShort = json.Where(x => x.Short != null).Select(x=> x.Short).ToArray(); - validShort.Should().HaveCountGreaterOrEqualTo(1); - validShort.Should().Contain(x => x.Data == @short); - - var validUShort = json.Where(x => x.Ushort != null).Select(x=> x.Ushort).ToArray(); - validUShort.Should().HaveCountGreaterOrEqualTo(1); - validUShort.Should().Contain(x => x.Data == @ushort); - - var validInt = json.Where(x => x.Int != null).Select(x=> x.Int).ToArray(); - validInt.Should().HaveCountGreaterOrEqualTo(1); - validInt.Should().Contain(x => x.Data == @int); - - var validUInt = json.Where(x => x.Uint != null).Select(x=> x.Uint).ToArray(); - validUInt.Should().HaveCountGreaterOrEqualTo(1); - validUInt.Should().Contain(x => x.Data == @uint); - - var validLong = json.Where(x => x.Long != null).Select(x=> x.Long).ToArray(); - validLong.Should().HaveCountGreaterOrEqualTo(1); - validLong.Should().Contain(x => x.Data == @long); - - var validULong = json.Where(x => x.Ulong != null).Select(x=> x.Ulong).ToArray(); - validULong.Should().HaveCountGreaterOrEqualTo(1); - validULong.Should().Contain(x => x.Data == @ulong); - - var validFloat = json.Where(x => x.Float != null).Select(x=> x.Float).ToArray(); - validFloat.Should().HaveCountGreaterOrEqualTo(1); - validFloat.Should().Contain(x => x.Data == @float); - - var validDouble = json.Where(x => x.Double != null).Select(x=> x.Double).ToArray(); - validDouble.Should().HaveCountGreaterOrEqualTo(1); - validDouble.Should().Contain(x => x.Data == @double); - - var validDecimal = json.Where(x => x.Decimal != null).Select(x=> x.Decimal).ToArray(); - validDecimal.Should().HaveCountGreaterOrEqualTo(1); - validDecimal.Should().Contain(x => x.Data == @decimal); - - var validString = json.Where(x => x.String != null).Select(x=> x.String).ToArray(); - validString.Should().HaveCountGreaterOrEqualTo(1); - validString.Should().Contain(x => x.Data == @string); - - var validDateTime = json.Where(x => x.DateTime != null).Select(x=> x.DateTime).ToArray(); - validDateTime.Should().HaveCountGreaterOrEqualTo(1); - validDateTime.Should().Contain(x => x.Data == dateTime); - - var validDateTimeOffset = json.Where(x => x.DateTimeOffset != null).Select(x=> x.DateTimeOffset).ToArray(); - validDateTimeOffset.Should().HaveCountGreaterOrEqualTo(1); - validDateTimeOffset.Should().Contain(x => x.Data == dateTimeOffset); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunNullAction(bool isNull) - { - var @bool = isNull ? null : new bool?(_fixture.Create()); - var @byte = isNull ? null : new byte?(_fixture.Create()); - var @sbyte = isNull ? null : new sbyte?(_fixture.Create()); - var @short = isNull ? null : new short?(_fixture.Create()); - var @ushort = isNull ? null : new ushort?(_fixture.Create()); - var @int = isNull ? null : new int?(_fixture.Create()); - var @uint = isNull ? null : new uint?(_fixture.Create()); - var @long = isNull ? null : new long?(_fixture.Create()); - var @ulong = isNull ? null : new ulong?(_fixture.Create()); - var @double = isNull ? null : new double?(_fixture.Create()); - var @float = isNull ? null : new float?(_fixture.Create()); - var @decimal = isNull ? null : new decimal?(_fixture.Create()); - var @string = isNull ? null : _fixture.Create(); - var @dateTime = isNull ? null : new DateTime?(_fixture.Create()); - var @dateTimeOffset = isNull ? null : new DateTimeOffset?(_fixture.Create()); - - var @boolS = isNull ? "null" : @bool.ToString().ToLower(); - var @byteS = isNull ? "null" : @byte.ToString(); - var @sbyteS = isNull ? "null" : @sbyte.ToString(); - var @shortS = isNull ? "null" : @short.ToString(); - var @ushortS = isNull ? "null" : @ushort.ToString(); - var @intS = isNull ? "null" : @int.ToString(); - var @uintS = isNull ? "null" : @uint.ToString(); - var @longS = isNull ? "null" : @long.ToString(); - var @ulongS = isNull ? "null" : @ulong.ToString(); - var @doubleS = isNull ? "null" : @double.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @floatS = isNull ? "null" : @float.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @decimalS = isNull ? "null" : @decimal.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @stringS = isNull ? "null" : $"\"{@string}\""; - var @dateTimeS = isNull ? "null" : $"\"{@dateTime:O}\""; - var @dateTimeOffsetS = isNull ? "null" : $"\"{@dateTimeOffset:O}\""; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PostAsync("/things/event-type/actions/runNull", - new StringContent($@" -{{ - ""runNull"": {{ - ""input"": {{ - ""bool"": {@boolS}, - ""byte"": {@byteS}, - ""sbyte"": {@sbyteS}, - ""short"": {@shortS}, - ""ushort"": {@ushortS}, - ""int"": {@intS}, - ""uint"": {@uintS}, - ""long"": {@longS}, - ""ulong"": {@ulongS}, - ""double"": {@doubleS}, - ""float"": {@floatS}, - ""decimal"": {@decimalS}, - ""string"": {@stringS}, - ""dateTime"": {@dateTimeS}, - ""dateTimeOffset"": {@dateTimeOffsetS} - }} - }} -}}", Encoding.UTF8, "application/json"), source.Token).ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.Created); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - response = await _client.GetAsync("/things/event-type/events", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be("application/json"); - - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JsonConvert.DeserializeObject>(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - var validBool = json.Where(x => x.NullableBool != null).Select(x => x.NullableBool).ToArray(); - validBool.Should().HaveCountGreaterOrEqualTo(1); - validBool.Should().Contain(x => x.Data == @bool); - - var validByte = json.Where(x => x.NullableByte != null).Select(x=> x.NullableByte).ToArray(); - validByte.Should().HaveCountGreaterOrEqualTo(1); - validByte.Should().Contain(x => x.Data == @byte); - - var validSByte = json.Where(x => x.NullableSbyte != null).Select(x=> x.NullableSbyte).ToArray(); - validSByte.Should().HaveCountGreaterOrEqualTo(1); - validSByte.Should().Contain(x => x.Data == @sbyte); - - var validShort = json.Where(x => x.NullableShort != null).Select(x=> x.NullableShort).ToArray(); - validShort.Should().HaveCountGreaterOrEqualTo(1); - validShort.Should().Contain(x => x.Data == @short); - - var validUShort = json.Where(x => x.NullableUshort != null).Select(x=> x.NullableUshort).ToArray(); - validUShort.Should().HaveCountGreaterOrEqualTo(1); - validUShort.Should().Contain(x => x.Data == @ushort); - - var validInt = json.Where(x => x.NullableInt != null).Select(x=> x.NullableInt).ToArray(); - validInt.Should().HaveCountGreaterOrEqualTo(1); - validInt.Should().Contain(x => x.Data == @int); - - var validUInt = json.Where(x => x.NullableUint != null).Select(x=> x.NullableUint).ToArray(); - validUInt.Should().HaveCountGreaterOrEqualTo(1); - validUInt.Should().Contain(x => x.Data == @uint); - - var validLong = json.Where(x => x.NullableLong != null).Select(x=> x.NullableLong).ToArray(); - validLong.Should().HaveCountGreaterOrEqualTo(1); - validLong.Should().Contain(x => x.Data == @long); - - var validULong = json.Where(x => x.NullableUlong != null).Select(x=> x.NullableUlong).ToArray(); - validULong.Should().HaveCountGreaterOrEqualTo(1); - validULong.Should().Contain(x => x.Data == @ulong); - - var validFloat = json.Where(x => x.NullableFloat != null).Select(x=> x.NullableFloat).ToArray(); - validFloat.Should().HaveCountGreaterOrEqualTo(1); - validFloat.Should().Contain(x => x.Data == @float); - - var validDouble = json.Where(x => x.NullableDouble != null).Select(x=> x.NullableDouble).ToArray(); - validDouble.Should().HaveCountGreaterOrEqualTo(1); - validDouble.Should().Contain(x => x.Data == @double); - - var validDecimal = json.Where(x => x.NullableDecimal != null).Select(x=> x.NullableDecimal).ToArray(); - validDecimal.Should().HaveCountGreaterOrEqualTo(1); - validDecimal.Should().Contain(x => x.Data == @decimal); - - var validString = json.Where(x => x.NullableString != null).Select(x=> x.NullableString).ToArray(); - validString.Should().HaveCountGreaterOrEqualTo(1); - validString.Should().Contain(x => x.Data == @string); - - var validDateTime = json.Where(x => x.NullableDateTime != null).Select(x=> x.NullableDateTime).ToArray(); - validDateTime.Should().HaveCountGreaterOrEqualTo(1); - validDateTime.Should().Contain(x => x.Data == dateTime); - - var validDateTimeOffset = json.Where(x => x.NullableDateTimeOffset != null).Select(x=> x.NullableDateTimeOffset).ToArray(); - validDateTimeOffset.Should().HaveCountGreaterOrEqualTo(1); - validDateTimeOffset.Should().Contain(x => x.Data == dateTimeOffset); - } - - public class Event - { - public T Data { get; set; } - public DateTime Timestamp { get; set; } - } - - public class Events - { - public Event Bool { get; set; } - public Event Byte { get; set; } - public Event Sbyte { get; set; } - public Event Short { get; set; } - public Event Ushort { get; set; } - public Event Int { get; set; } - public Event Uint { get; set; } - public Event Long { get; set; } - public Event Ulong { get; set; } - public Event Double { get; set; } - public Event Float { get; set; } - public Event Decimal { get; set; } - public Event String { get; set; } - public Event DateTime { get; set; } - public Event DateTimeOffset { get; set; } - - public Event NullableBool { get; set; } - public Event NullableByte { get; set; } - public Event NullableSbyte { get; set; } - public Event NullableShort { get; set; } - public Event NullableUshort { get; set; } - public Event NullableInt { get; set; } - public Event NullableUint { get; set; } - public Event NullableLong { get; set; } - public Event NullableUlong { get; set; } - public Event NullableDouble { get; set; } - public Event NullableFloat { get; set; } - public Event NullableDecimal { get; set; } - public Event NullableString { get; set; } - public Event NullableDateTime { get; set; } - public Event NullableDateTimeOffset { get; set; } - - } - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Events.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Events.cs deleted file mode 100644 index 750fde1..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Events.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class Events - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - - public Events() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - #region GET - - [Fact] - public async Task GetAll() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync("/things/event/events", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be("application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - - if (((JArray)json).Count == 0) - { - await Task.Delay(3_000).ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - response = await _client.GetAsync("/things/event/events", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCountGreaterOrEqualTo(1); - } - - - var obj = ((JArray)json)[0] as JObject; - obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Object); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Integer); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Value().Should().Be(0); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("timestamp", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Date); - - } - - [Fact] - public async Task GetEvent() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync("/things/event/events/overheated", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - - if (((JArray)json).Count == 0) - { - await Task.Delay(3_000).ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - response = await _client.GetAsync("/things/event/events", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCountGreaterOrEqualTo(1); - } - - var obj = ((JArray)json)[0] as JObject; - obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Object); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Integer); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Value().Should().Be(0); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("timestamp", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Date); - } - - [Fact] - public async Task GetInvalidEvent() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync("/things/event/events/aaaaa", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - - } - #endregion - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Properties.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Properties.cs deleted file mode 100644 index 25a0243..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Properties.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class Properties - { - private readonly Fixture _fixture; - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - public Properties() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - #region GET - [Fact] - public async Task GetAll() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync("/things/property/properties", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse(@" -{ - ""on"": false, - ""brightness"": 0, - ""reader"": 0 -} -")); - } - - [Theory] - [InlineData("on", false)] - [InlineData("brightness", 0)] - public async Task Get(string property, object value) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/property/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value.ToString().ToLower()} }}")); - } - - [Fact] - public async Task GetInvalid() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/property/properties/{_fixture.Create()}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } - #endregion - - #region PUT - - [Theory] - [InlineData("on", true)] - [InlineData("brightness", 10)] - public async Task Put(string property, object value) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property/properties/{property}", - new StringContent($@"{{ ""{property}"": {value.ToString().ToLower()} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value.ToString().ToLower()} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value.ToString().ToLower()} }}")); - } - - [Theory] - [InlineData("brightness", -1, 0)] - [InlineData("brightness", 101, 0)] - [InlineData("reader", 101, 0)] - public async Task PutInvalidValue(string property, object value, object defaultValue) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property/properties/{property}", - new StringContent($@"{{ ""{property}"": {value.ToString().ToLower()} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - response = await _client.GetAsync($"/things/property/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {defaultValue.ToString().ToLower()} }}")); - } - - [Fact] - public async Task PutInvalidProperty() - { - var property = _fixture.Create(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - var response = await _client.PutAsync($"/things/property/properties/{property}", - new StringContent($@"{{ ""{property}"": {_fixture.Create()} }}"), source.Token); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } - - #endregion - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesEnumType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesEnumType.cs deleted file mode 100644 index f51eff0..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesEnumType.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class PropertiesEnumType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30_000); - private readonly HttpClient _client; - public PropertiesEnumType() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - #region PUT - - [Theory] - [InlineData("numberByte", 0)] - [InlineData("numberByte", byte.MaxValue)] - [InlineData("numberByte", byte.MinValue)] - [InlineData("numberSByte", 0)] - [InlineData("numberSByte", sbyte.MaxValue)] - [InlineData("numberSByte", sbyte.MinValue)] - [InlineData("numberShort", 0)] - [InlineData("numberShort", short.MaxValue)] - [InlineData("numberShort", short.MinValue)] - [InlineData("numberUShort", 0)] - [InlineData("numberUShort", ushort.MaxValue)] - [InlineData("numberUShort", ushort.MinValue)] - [InlineData("numberInt", 0)] - [InlineData("numberInt", int.MaxValue)] - [InlineData("numberInt", int.MinValue)] - [InlineData("numberUInt", 0)] - [InlineData("numberUInt", uint.MaxValue)] - [InlineData("numberUInt", uint.MinValue)] - [InlineData("numberLong", 0)] - [InlineData("numberLong", long.MaxValue)] - [InlineData("numberLong", long.MinValue)] - [InlineData("numberULong", 0)] - [InlineData("numberULong", ulong.MaxValue)] - [InlineData("numberULong", ulong.MinValue)] - [InlineData("numberDouble", 0)] - [InlineData("numberDouble", double.MaxValue)] - [InlineData("numberDouble", double.MinValue)] - [InlineData("numberFloat", 0)] - [InlineData("numberFloat", float.MaxValue)] - [InlineData("numberFloat", float.MinValue)] - [InlineData("bool", true)] - [InlineData("bool", false)] - [InlineData("nullableBool", null)] - [InlineData("nullableBool", true)] - [InlineData("nullableBool", false)] - [InlineData("nullableByte", null)] - [InlineData("nullableByte", byte.MaxValue)] - [InlineData("nullableByte", byte.MinValue)] - [InlineData("nullableSByte", null)] - [InlineData("nullableSByte", sbyte.MinValue)] - [InlineData("nullableSByte", sbyte.MaxValue)] - [InlineData("nullableShort", null)] - [InlineData("nullableShort", short.MinValue)] - [InlineData("nullableShort", short.MaxValue)] - [InlineData("nullableUShort", null)] - [InlineData("nullableUShort", ushort.MinValue)] - [InlineData("nullableUShort", ushort.MaxValue)] - [InlineData("nullableInt", null)] - [InlineData("nullableInt", int.MinValue)] - [InlineData("nullableInt", int.MaxValue)] - [InlineData("nullableUInt", null)] - [InlineData("nullableUInt", uint.MinValue)] - [InlineData("nullableUInt", uint.MaxValue)] - [InlineData("nullableLong", null)] - [InlineData("nullableLong", long.MinValue)] - [InlineData("nullableLong", long.MaxValue)] - [InlineData("nullableULong", null)] - [InlineData("nullableULong", ulong.MinValue)] - [InlineData("nullableULong", ulong.MaxValue)] - [InlineData("nullableDouble", null)] - [InlineData("nullableDouble", double.MinValue)] - [InlineData("nullableDouble", double.MaxValue)] - [InlineData("nullableFloat", null)] - [InlineData("nullableFloat", float.MinValue)] - [InlineData("nullableFloat", float.MaxValue)] - [InlineData("nullableDecimal", null)] - public async Task PutNumber(string property, object value) - { - value = value != null ? value.ToString().ToLower() : "null"; - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property-enum-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-enum-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - [Theory] - [InlineData("text", "ola")] - [InlineData("text", "ass")] - [InlineData("text", "aaa")] - [InlineData("text", null)] - public async Task PutStringValue(string property, string value) - { - value = value != null ? $"\"{value}\"" : "null"; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property-enum-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-enum-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - - #endregion - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesType.cs deleted file mode 100644 index f7dca1c..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesType.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class PropertiesType - { - private readonly Fixture _fixture; - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30_000); - private readonly HttpClient _client; - public PropertiesType() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - #region PUT - - [Theory] - [InlineData("numberByte", typeof(byte))] - [InlineData("numberSByte", typeof(sbyte))] - [InlineData("numberShort", typeof(short))] - [InlineData("numberUShort", typeof(ushort))] - [InlineData("numberInt", typeof(int))] - [InlineData("numberUInt", typeof(uint))] - [InlineData("numberLong", typeof(long))] - [InlineData("numberULong", typeof(ulong))] - [InlineData("numberDouble", typeof(double))] - [InlineData("numberFloat", typeof(float))] - [InlineData("numberDecimal", typeof(decimal))] - [InlineData("bool", typeof(bool))] - [InlineData("nullableBool", typeof(bool?))] - [InlineData("nullableByte", typeof(byte?))] - [InlineData("nullableSByte", typeof(sbyte?))] - [InlineData("nullableShort", typeof(short?))] - [InlineData("nullableUShort", typeof(ushort?))] - [InlineData("nullableInt", typeof(int?))] - [InlineData("nullableUInt", typeof(uint?))] - [InlineData("nullableLong", typeof(long?))] - [InlineData("nullableULong", typeof(ulong?))] - [InlineData("nullableDouble", typeof(double?))] - [InlineData("nullableFloat", typeof(float?))] - [InlineData("nullableDecimal", typeof(decimal?))] - public async Task PutNumber(string property, Type type) - { - var value = CreateValue(type)?.ToString().ToLower(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value ?? "null"} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - [Theory] - [InlineData("data", typeof(DateTime))] - [InlineData("dataOffset", typeof(DateTimeOffset))] - [InlineData("nullableData", typeof(DateTime?))] - [InlineData("nullableDataOffset", typeof(DateTimeOffset?))] - [InlineData("text", typeof(string))] - public async Task PutStringValue(string property, Type type) - { - var value = CreateValue(type); - - if (value != null && (type == typeof(DateTime) - || type == typeof(DateTime?))) - { - value = ((DateTime)value).ToString("O"); - } - - if (value != null && (type == typeof(DateTimeOffset) - || type == typeof(DateTimeOffset?))) - { - value = ((DateTimeOffset)value).ToString("O"); - } - - value = value != null ? $"\"{value}\"" : "null"; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - var response = await _client.PutAsync($"/things/property-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - - #endregion - - private object CreateValue(Type type) - { - if (type == typeof(bool)) - { - return _fixture.Create(); - } - - if (type == typeof(bool?)) - { - return _fixture.Create(); - } - - if (type == typeof(byte)) - { - return _fixture.Create(); - } - - if (type == typeof(byte?)) - { - return _fixture.Create(); - } - - if (type == typeof(sbyte)) - { - return _fixture.Create(); - } - - if (type == typeof(sbyte?)) - { - return _fixture.Create(); - } - - if (type == typeof(short)) - { - return _fixture.Create(); - } - - if (type == typeof(short?)) - { - return _fixture.Create(); - } - - if (type == typeof(ushort)) - { - return _fixture.Create(); - } - - if (type == typeof(ushort?)) - { - return _fixture.Create(); - } - - if (type == typeof(int)) - { - return _fixture.Create(); - } - - if (type == typeof(int?)) - { - return _fixture.Create(); - } - - if (type == typeof(uint)) - { - return _fixture.Create(); - } - - if (type == typeof(uint?)) - { - return _fixture.Create(); - } - - if (type == typeof(long)) - { - return _fixture.Create(); - } - - if (type == typeof(long?)) - { - return _fixture.Create(); - } - - if (type == typeof(ulong)) - { - return _fixture.Create(); - } - - if (type == typeof(ulong?)) - { - return _fixture.Create(); - } - - if (type == typeof(double)) - { - return _fixture.Create(); - } - - if (type == typeof(double?)) - { - return _fixture.Create(); - } - - if (type == typeof(float)) - { - return _fixture.Create(); - } - - if (type == typeof(float?)) - { - return _fixture.Create(); - } - - if (type == typeof(decimal)) - { - return _fixture.Create(); - } - - if (type == typeof(decimal?)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTime)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTime?)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTimeOffset)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTimeOffset?)) - { - return _fixture.Create(); - } - - return _fixture.Create(); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesValidation.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesValidation.cs deleted file mode 100644 index 5139566..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/PropertiesValidation.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class PropertiesValidation - { - private readonly Fixture _fixture; - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30_000); - private readonly HttpClient _client; - public PropertiesValidation() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - #region PUT - - [Theory] - [InlineData("numberByte", 1)] - [InlineData("numberByte", 10)] - [InlineData("numberByte", 100)] - [InlineData("numberSByte", 1)] - [InlineData("numberSByte", 10)] - [InlineData("numberSByte", 100)] - [InlineData("numberShort", 1)] - [InlineData("numberShort", 10)] - [InlineData("numberShort", 100)] - [InlineData("numberUShort", 1)] - [InlineData("numberUShort", 10)] - [InlineData("numberUShort", 100)] - [InlineData("numberInt", 1)] - [InlineData("numberInt", 10)] - [InlineData("numberInt", 100)] - [InlineData("numberUInt", 1)] - [InlineData("numberUInt", 10)] - [InlineData("numberUInt", 100)] - [InlineData("numberLong", 1)] - [InlineData("numberLong", 10)] - [InlineData("numberLong", 100)] - [InlineData("numberULong", 1)] - [InlineData("numberULong", 10)] - [InlineData("numberULong", 100)] - [InlineData("numberDouble", 1)] - [InlineData("numberDouble", 10)] - [InlineData("numberDouble", 100)] - [InlineData("numberFloat", 1)] - [InlineData("numberFloat", 10)] - [InlineData("numberFloat", 100)] - [InlineData("numberDecimal", 1)] - [InlineData("numberDecimal", 10)] - [InlineData("numberDecimal", 100)] - [InlineData("nullableByte", 1)] - [InlineData("nullableByte", 10)] - [InlineData("nullableByte", 100)] - [InlineData("nullableSByte", 1)] - [InlineData("nullableSByte", 10)] - [InlineData("nullableSByte", 100)] - [InlineData("nullableShort", 1)] - [InlineData("nullableShort", 10)] - [InlineData("nullableShort", 100)] - [InlineData("nullableUShort", 1)] - [InlineData("nullableUShort", 10)] - [InlineData("nullableUShort", 100)] - [InlineData("nullableInt", 1)] - [InlineData("nullableInt", 10)] - [InlineData("nullableInt", 100)] - [InlineData("nullableUInt", 1)] - [InlineData("nullableUInt", 10)] - [InlineData("nullableUInt", 100)] - [InlineData("nullableLong", 1)] - [InlineData("nullableLong", 10)] - [InlineData("nullableLong", 100)] - [InlineData("nullableULong", 1)] - [InlineData("nullableULong", 10)] - [InlineData("nullableULong", 100)] - [InlineData("nullableDouble", 1)] - [InlineData("nullableDouble", 10)] - [InlineData("nullableDouble", 100)] - [InlineData("nullableFloat", 1)] - [InlineData("nullableFloat", 10)] - [InlineData("nullableFloat", 100)] - [InlineData("nullableDecimal", 1)] - [InlineData("nullableDecimal", 10)] - [InlineData("nullableDecimal", 100)] - public async Task PutNumber(string property, object value) - { - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property-validation-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-validation-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - [Theory] - [InlineData("numberByte", 0)] - [InlineData("numberByte", 101)] - [InlineData("numberSByte", 0)] - [InlineData("numberSByte", 101)] - [InlineData("numberShort", 0)] - [InlineData("numberShort", 101)] - [InlineData("numberUShort", 0)] - [InlineData("numberUShort", 101)] - [InlineData("numberInt", 0)] - [InlineData("numberInt", 101)] - [InlineData("numberUInt", 0)] - [InlineData("numberUInt", 101)] - [InlineData("numberLong", 0)] - [InlineData("numberLong", 101)] - [InlineData("numberULong", 0)] - [InlineData("numberULong", 101)] - [InlineData("numberDouble", 0)] - [InlineData("numberDouble", 101)] - [InlineData("numberFloat", 0)] - [InlineData("numberFloat", 101)] - [InlineData("numberDecimal", 0)] - [InlineData("numberDecimal", 101)] - [InlineData("nullableByte", 0)] - [InlineData("nullableByte", 101)] - [InlineData("nullableSByte", 0)] - [InlineData("nullableSByte", 101)] - [InlineData("nullableShort", 0)] - [InlineData("nullableShort", 101)] - [InlineData("nullableUShort", 0)] - [InlineData("nullableUShort", 101)] - [InlineData("nullableInt", 0)] - [InlineData("nullableInt", 101)] - [InlineData("nullableUInt", 0)] - [InlineData("nullableUInt", 101)] - [InlineData("nullableLong", 0)] - [InlineData("nullableLong", 101)] - [InlineData("nullableULong", 0)] - [InlineData("nullableULong", 101)] - [InlineData("nullableDouble", 0)] - [InlineData("nullableDouble", 101)] - [InlineData("nullableFloat", 0)] - [InlineData("nullableFloat", 101)] - [InlineData("nullableDecimal", 0)] - [InlineData("nullableDecimal", 101)] - public async Task PutInvalidNumber(string property, object value) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.PutAsync($"/things/property-validation-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Theory] - [InlineData("text", "abc")] - [InlineData("email", "text@test.com")] - public async Task PutStringValue(string property, string value) - { - value = value != null ? $"\"{value}\"" : "null"; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - var response = await _client.PutAsync($"/things/property-validation-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - response = await _client.GetAsync($"/things/property-validation-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - [Theory] - [InlineData("text", "")] - [InlineData("text", null)] - [InlineData("email", "text")] - [InlineData("email", null)] - public async Task PutInvalidString(string property, string value) - { - value = value != null ? $"\"{value}\"" : "null"; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - - var response = await _client.PutAsync($"/things/property-validation-type/properties/{property}", - new StringContent($@"{{ ""{property}"": {value} }}"), source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - #endregion - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Thing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Thing.cs deleted file mode 100644 index efeecb3..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Http/Thing.cs +++ /dev/null @@ -1,1233 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Hosting; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Http -{ - public class Thing - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly HttpClient _client; - public Thing() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - } - - [Fact(Skip = "To improve")] - public async Task GetAll() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync("/things", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCount(5); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse(@" -[ - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/lamp"", - ""title"": ""My Lamp"", - ""description"": ""A web connected lamp"", - ""@type"": [ - ""Light"", - ""OnOffSwitch"" - ], - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/lamp/properties/brightness"" - } - ] - } - }, - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/lamp/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/lamp/actions/longRun"" - } - ] - } - }, - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/lamp/events/overheated"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/lamp/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/lamp/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/lamp/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/lamp"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/property"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/event"", - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/event/events/overheated"" - } - ] - }, - ""otherEvent"": { - ""title"": ""OtherEvent"", - ""type"": ""string"", - ""links"": [ - { - ""href"": ""/things/event/events/otherEvent"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/event/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/event/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/event/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/event"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/action"", - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/action/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/action/actions/longRun"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/action/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/action/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/action/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/action"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/web-socket-property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/web-socket-property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/web-socket-property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/web-socket-property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/web-socket-property"" - } - ] - } -] -")); - } - - [Theory] - [InlineData("lamp", LAMP)] - [InlineData("property", PROPERTY)] - [InlineData("event", EVENT)] - [InlineData("action", ACTION)] - [InlineData("web-socket-property", WEB_SOCKET_PROPERTY)] - public async Task Get(string thing, string expected) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/{thing}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse(expected)); - } - - [Fact] - public async Task GetInvalid() - { - var fixture = new Fixture(); - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/{fixture.Create()}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeFalse(); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } - - [Fact(Skip = "To improve")] - public async Task GetAllWhenUseThingAdapter() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var host = await Program.CreateHostBuilder(null, opt => opt.UseThingAdapterUrl = true) - .StartAsync(source.Token) - .ConfigureAwait(false); - - var client = host.GetTestServer().CreateClient(); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - var response = await client.GetAsync("/things", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync(); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCount(5); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse(@" -[ - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""lamp"", - ""href"": ""/things/lamp"", - ""base"": ""http://localhost/things/lamp"", - ""title"": ""My Lamp"", - ""description"": ""A web connected lamp"", - ""@type"": [ - ""Light"", - ""OnOffSwitch"" - ], - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/lamp/properties/brightness"" - } - ] - } - }, - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/lamp/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/lamp/actions/longRun"" - } - ] - } - }, - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/lamp/events/overheated"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/lamp/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/lamp/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/lamp/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/lamp"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""property"", - ""href"": ""/things/property"", - ""base"": ""http://localhost/things/property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/property"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""event"", - ""href"": ""/things/event"", - ""base"": ""http://localhost/things/event"", - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/event/events/overheated"" - } - ] - }, - ""otherEvent"": { - ""title"": ""OtherEvent"", - ""type"": ""string"", - ""links"": [ - { - ""href"": ""/things/event/events/otherEvent"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/event/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/event/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/event/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/event"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""action"", - ""href"": ""/things/action"", - ""base"": ""http://localhost/things/action"", - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/action/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/action/actions/longRun"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/action/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/action/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/action/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/action"" - } - ] - }, - { - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""web-socket-property"", - ""href"": ""/things/web-socket-property"", - ""base"": ""http://localhost/things/web-socket-property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/web-socket-property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/web-socket-property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/web-socket-property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/web-socket-property"" - } - ] - } -] -")); - } - - [Fact] - public async Task GetWhenUseThingAdapter() - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var host = await Program.CreateHostBuilder(null, opt => opt.UseThingAdapterUrl = true) - .StartAsync(source.Token) - .ConfigureAwait(false); - - var client = host.GetTestServer().CreateClient(); - - var response = await client.GetAsync("/things/Lamp", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse(@" -{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""lamp"", - ""href"": ""/things/lamp"", - ""base"": ""http://localhost/things/lamp"", - ""title"": ""My Lamp"", - ""description"": ""A web connected lamp"", - ""@type"": [ - ""Light"", - ""OnOffSwitch"" - ], - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/lamp/properties/brightness"" - } - ] - } - }, - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/lamp/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/lamp/actions/longRun"" - } - ] - } - }, - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/lamp/events/overheated"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/lamp/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/lamp/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/lamp/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/lamp"" - } - ] -} -")); - } - - - private const string WEB_SOCKET_PROPERTY = @"{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/web-socket-property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/web-socket-property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/web-socket-property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/web-socket-property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/web-socket-property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/web-socket-property"" - } - ] - }"; - - private const string ACTION = @"{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/action"", - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/action/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/action/actions/longRun"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/action/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/action/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/action/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/action"" - } - ] - }"; - private const string EVENT = @"{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/event"", - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/event/events/overheated"" - } - ] - }, - ""otherEvent"": { - ""title"": ""OtherEvent"", - ""type"": ""string"", - ""links"": [ - { - ""href"": ""/things/event/events/otherEvent"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/event/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/event/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/event/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/event"" - } - ] - }"; - - private const string PROPERTY = @"{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/property"", - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/property/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/property/properties/brightness"" - } - ] - }, - ""reader"": { - ""readOnly"": true, - ""links"": [ - { - ""href"": ""/things/property/properties/reader"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/property/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/property/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/property/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/property"" - } - ] - }"; - - private const string LAMP = @" -{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/lamp"", - ""title"": ""My Lamp"", - ""description"": ""A web connected lamp"", - ""@type"": [ - ""Light"", - ""OnOffSwitch"" - ], - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""type"": ""boolean"", - ""@type"": ""OnOffProperty"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""type"": ""integer"", - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""links"": [ - { - ""href"": ""/things/lamp/properties/brightness"" - } - ] - } - }, - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/lamp/actions/fade"" - } - ] - }, - ""longRun"": { - ""links"": [ - { - ""href"": ""/things/lamp/actions/longRun"" - } - ] - } - }, - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/lamp/events/overheated"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/lamp/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/lamp/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/lamp/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/lamp"" - } - ] - }"; - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Mozilla.IoT.WebThing.AcceptanceTest.csproj b/test/Mozilla.IoT.WebThing.AcceptanceTest/Mozilla.IoT.WebThing.AcceptanceTest.csproj deleted file mode 100644 index 6ad2865..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Mozilla.IoT.WebThing.AcceptanceTest.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - netcoreapp3.1 - false - - - - - - - - - - - - - - - - - - - diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Startup.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Startup.cs deleted file mode 100644 index eac17e0..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Startup.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.WebSockets; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Mozilla.IoT.WebThing.AcceptanceTest.Things; -using Mozilla.IoT.WebThing.Extensions; - -namespace Mozilla.IoT.WebThing.AcceptanceTest -{ - public class Startup - { - public static Action? Option { get; set; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddThings(Option) - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - .AddThing() - ; - - services.AddWebSockets(o => { }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseWebSockets(); - - app.UseEndpoints(endpoints => - { - endpoints.MapThings(); - }); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionThing.cs deleted file mode 100644 index aa13421..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionThing.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class ActionThing : Thing - { - - public override string Name => "action"; - - [ThingAction(Name = "fade", Title = "Fade", Type = new []{"FadeAction"}, - Description = "Fade the lamp to a given level")] - public void Fade( - [ThingParameter(Minimum = 0, Maximum = 100)]int level, - [ThingParameter(Minimum = 0, Unit = "milliseconds")]int duration) - { - - } - - public Task LongRun() - { - return Task.Delay(3_000); - } - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionTypeThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionTypeThing.cs deleted file mode 100644 index b6cec49..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/ActionTypeThing.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class ActionTypeThing : Thing - { - public override string Name => "action-type"; - - public void Run( - bool @bool, - byte @byte, - sbyte @sbyte, - short @short, - ushort @ushort, - int @int, - uint @uint, - long @long, - ulong @ulong, - double @double, - float @float, - decimal @decimal, - string @string, - DateTime @dateTime, - DateTimeOffset @dateTimeOffset, - [FromServices]ILogger logger - ) - { - logger.LogInformation("Execution action...."); - } - - public void RunNull( - bool? @bool, - byte? @byte, - sbyte? @sbyte, - short? @short, - ushort? @ushort, - int? @int, - uint? @uint, - long? @long, - ulong? @ulong, - double? @double, - float? @float, - decimal? @decimal, - string? @string, - DateTime? @dateTime, - DateTimeOffset? @dateTimeOffset, - [FromServices]ILogger logger - ) - { - logger.LogInformation("Execution action...."); - } - - public void RunWithValidation( - [ThingParameter(Minimum = 1, Maximum = 100)]byte @byte, - [ThingParameter(Minimum = 1, Maximum = 100)]sbyte @sbyte, - [ThingParameter(Minimum = 1, Maximum = 100)]short @short, - [ThingParameter(Minimum = 1, Maximum = 100)]ushort @ushort, - [ThingParameter(Minimum = 1, Maximum = 100)]int @int, - [ThingParameter(Minimum = 1, Maximum = 100)]uint @uint, - [ThingParameter(Minimum = 1, Maximum = 100)]long @long, - [ThingParameter(Minimum = 1, Maximum = 100)]ulong @ulong, - [ThingParameter(Minimum = 1, Maximum = 100)]float @float, - [ThingParameter(Minimum = 1, Maximum = 100)]double @double, - //[ThingParameter(Minimum = 1, Maximum = 100, MultipleOf = 2)]decimal @decimal, - [FromServices]ILogger logger - ) - { - logger.LogInformation("Execution action...."); - } - - public void RunWithValidationExclusive( - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]byte @byte, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]sbyte @sbyte, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]short @short, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]ushort @ushort, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]int @int, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]uint @uint, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]long @long, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]ulong @ulong, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]float @float, - [ThingParameter(ExclusiveMinimum = 1, ExclusiveMaximum = 100)]double @double, - //[ThingParameter(Minimum = 1, Maximum = 100, MultipleOf = 2)]decimal @decimal, - [FromServices]ILogger logger - ) - { - logger.LogInformation("Execution action...."); - } - - public void RunWithStringValidation( - [ThingParameter(MinimumLength = 1, MaximumLength = 10)]string @minAnMax, - [ThingParameter(Pattern = @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$")]string mail, - [FromServices]ILogger logger - ) - { - logger.LogInformation("Execution action...."); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThing.cs deleted file mode 100644 index b7619e7..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThing.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading.Tasks; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class EventThing : Thing - { - public EventThing() - { - Task.Factory.StartNew(() => - { - while (true) - { - Task.Delay(3_000).GetAwaiter().GetResult(); - var @event = Overheated; - @event?.Invoke(this, 0); - } - }); - - Task.Factory.StartNew(() => - { - while (true) - { - Task.Delay(4_000).GetAwaiter().GetResult(); - var @event = OtherEvent; - @event?.Invoke(this, 1.ToString()); - } - }); - } - - public override string Name => "event"; - - [ThingEvent(Title = "Overheated", - Type = new [] {"OverheatedEvent"}, - Description = "The lamp has exceeded its safe operating temperature")] - public event EventHandler Overheated; - - [ThingEvent(Title = "OtherEvent")] - public event EventHandler OtherEvent; - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThingType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThingType.cs deleted file mode 100644 index 02702bd..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/EventThingType.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class EventTypeThing : Thing - { - public override string Name => "event-type"; - - public event EventHandler Bool; - public event EventHandler Byte; - public event EventHandler SByte; - public event EventHandler Short; - public event EventHandler UShort; - public event EventHandler Int; - public event EventHandler UInt; - public event EventHandler Long; - public event EventHandler ULong; - public event EventHandler Float; - public event EventHandler Double; - public event EventHandler Decimal; - public event EventHandler String; - public event EventHandler DateTime; - public event EventHandler DateTimeOffset; - - public event EventHandler NullableBool; - public event EventHandler NullableByte; - public event EventHandler NullableSByte; - public event EventHandler NullableShort; - public event EventHandler NullableUShort; - public event EventHandler NullableInt; - public event EventHandler NullableUInt; - public event EventHandler NullableLong; - public event EventHandler NullableULong; - public event EventHandler NullableFloat; - public event EventHandler NullableDouble; - public event EventHandler NullableDecimal; - public event EventHandler NullableString; - public event EventHandler NullableDateTime; - public event EventHandler NullableDateTimeOffset; - - - public void Run( - bool @bool, - byte @byte, - sbyte @sbyte, - short @short, - ushort @ushort, - int @int, - uint @uint, - long @long, - ulong @ulong, - double @double, - float @float, - decimal @decimal, - string @string, - DateTime @dateTime, - DateTimeOffset @dateTimeOffset - ) - { - Bool?.Invoke(this, @bool); - Byte?.Invoke(this, @byte); - SByte?.Invoke(this, @sbyte); - Short?.Invoke(this, @short); - UShort?.Invoke(this, @ushort); - Int?.Invoke(this, @int); - UInt?.Invoke(this, @uint); - Long?.Invoke(this, @long); - ULong?.Invoke(this, @ulong); - Float?.Invoke(this, @float); - Double?.Invoke(this, @double); - Decimal?.Invoke(this, @decimal); - String?.Invoke(this, @string); - DateTime?.Invoke(this, dateTime); - DateTimeOffset?.Invoke(this, dateTimeOffset); - } - - public void RunNull( - bool? @bool, - byte? @byte, - sbyte? @sbyte, - short? @short, - ushort? @ushort, - int? @int, - uint? @uint, - long? @long, - ulong? @ulong, - double? @double, - float? @float, - decimal? @decimal, - string? @string, - DateTime? @dateTime, - DateTimeOffset? @dateTimeOffset - ) - { - NullableBool?.Invoke(this, @bool); - NullableByte?.Invoke(this, @byte); - NullableSByte?.Invoke(this, @sbyte); - NullableShort?.Invoke(this, @short); - NullableUShort?.Invoke(this, @ushort); - NullableInt?.Invoke(this, @int); - NullableUInt?.Invoke(this, @uint); - NullableLong?.Invoke(this, @long); - NullableULong?.Invoke(this, @ulong); - NullableFloat?.Invoke(this, @float); - NullableDouble?.Invoke(this, @double); - NullableDecimal?.Invoke(this, @decimal); - NullableString?.Invoke(this, @string); - NullableDateTime?.Invoke(this, dateTime); - NullableDateTimeOffset?.Invoke(this, dateTimeOffset); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/LampThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/LampThing.cs deleted file mode 100644 index 44e0c9c..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/LampThing.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading.Tasks; -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class LampThing : Thing - { - public override string Name => "Lamp"; - public override string Title => "My Lamp"; - public override string Description => "A web connected lamp"; - public override string[] Type { get; } = new[] { "Light", "OnOffSwitch" }; - - private bool _on; - [ThingProperty(Type = new[] {"OnOffProperty"}, Title = "On/Off", Description = "Whether the lamp is turned on")] - public bool On - { - get => _on; - set - { - _on = value; - OnPropertyChanged(); - } - } - - private int _brightness; - - [ThingProperty(Type = new[] {"BrightnessProperty"}, Title = "Brightness", - Description = "The level of light from 0-100", Minimum = 0, Maximum = 100)] - public int Brightness - { - get => _brightness; - set - { - _brightness = value; - OnPropertyChanged(); - } - } - - [ThingEvent(Title = "Overheated", - Type = new [] {"OverheatedEvent"}, - Description = "The lamp has exceeded its safe operating temperature")] - public event EventHandler Overheated; - - [ThingAction(Name = "fade", Title = "Fade", Type = new []{"FadeAction"}, - Description = "Fade the lamp to a given level")] - public void Fade( - [ThingParameter(Minimum = 0, Maximum = 100)]int level, - [ThingParameter(Minimum = 0, Unit = "milliseconds")]int duration) - { - - } - - public Task LongRun() - { - return Task.Delay(3_000); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyEnumThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyEnumThing.cs deleted file mode 100644 index 389f6b0..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyEnumThing.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class PropertyEnumThing : Thing - { - public override string Name => "property-enum-type"; - - private bool _bool; - [ThingProperty(Enum = new object[]{ true, false})] - public bool Bool - { - get => _bool; - set - { - _bool = value; - OnPropertyChanged(); - } - } - - private bool? _nullableBool; - [ThingProperty(Enum = new object[]{ null, true, false})] - public bool? NullableBool - { - get => _nullableBool; - set - { - _nullableBool = value; - OnPropertyChanged(); - } - } - - private byte _numberByte; - [ThingProperty(Enum = new object[]{ 0, byte.MaxValue, byte.MinValue })] - public byte NumberByte - { - get => _numberByte; - set - { - _numberByte = value; - OnPropertyChanged(); - } - } - - private byte? _nullableByte; - [ThingProperty(Enum = new object[]{ null, byte.MaxValue, byte.MinValue })] - public byte? NullableByte - { - get => _nullableByte; - set - { - _nullableByte = value; - OnPropertyChanged(); - } - } - - private sbyte _numberSByte; - [ThingProperty(Enum = new object[]{ 0, sbyte.MaxValue, sbyte.MinValue })] - public sbyte NumberSByte - { - get => _numberSByte; - set - { - _numberSByte = value; - OnPropertyChanged(); - } - } - - private sbyte? _nullableSByte; - [ThingProperty(Enum = new object[]{ null, sbyte.MaxValue, sbyte.MinValue })] - public sbyte? NullableSByte - { - get => _nullableSByte; - set - { - _nullableSByte = value; - OnPropertyChanged(); - } - } - - private short _numberShort; - [ThingProperty(Enum = new object[]{ 0, short.MaxValue, short.MinValue })] - public short NumberShort - { - get => _numberShort; - set - { - _numberShort = value; - OnPropertyChanged(); - } - } - - private short? _nullableShort; - [ThingProperty(Enum = new object[]{ null, short.MaxValue, short.MinValue })] - public short? NullableShort - { - get => _nullableShort; - set - { - _nullableShort = value; - OnPropertyChanged(); - } - } - - private ushort _numberUShort; - [ThingProperty(Enum = new object[]{ 0, ushort.MaxValue, ushort.MinValue })] - public ushort NumberUShort - { - get => _numberUShort; - set - { - _numberUShort = value; - OnPropertyChanged(); - } - } - - private ushort? _nullableUShort; - [ThingProperty(Enum = new object[]{ null, ushort.MaxValue, ushort.MinValue })] - public ushort? NullableUShort - { - get => _nullableUShort; - set - { - _nullableUShort = value; - OnPropertyChanged(); - } - } - - private int _numberInt; - [ThingProperty(Enum = new object[]{ 0, int.MaxValue, int.MinValue })] - public int NumberInt - { - get => _numberInt; - set - { - _numberInt = value; - OnPropertyChanged(); - } - } - - private int? _nullableInt; - [ThingProperty(Enum = new object[]{ null, int.MaxValue, int.MinValue })] - public int? NullableInt - { - get => _nullableInt; - set - { - _nullableInt = value; - OnPropertyChanged(); - } - } - - private uint _numberUInt; - [ThingProperty(Enum = new object[]{ 0, uint.MaxValue, uint.MinValue })] - public uint NumberUInt - { - get => _numberUInt; - set - { - _numberUInt = value; - OnPropertyChanged(); - } - } - - private uint? _nullableUInt; - [ThingProperty(Enum = new object[]{ null, uint.MaxValue, uint.MinValue })] - public uint? NullableUInt - { - get => _nullableUInt; - set - { - _nullableUInt = value; - OnPropertyChanged(); - } - } - - private long _numberLong; - [ThingProperty(Enum = new object[]{ 0, long.MaxValue, long.MinValue })] - public long NumberLong - { - get => _numberLong; - set - { - _numberLong = value; - OnPropertyChanged(); - } - } - - private long? _nullableLong; - [ThingProperty(Enum = new object[]{ null, long.MaxValue, long.MinValue })] - public long? NullableLong - { - get => _nullableLong; - set - { - _nullableLong = value; - OnPropertyChanged(); - } - } - - private ulong _numberULong; - [ThingProperty(Enum = new object[]{ 0, ulong.MaxValue, ulong.MinValue})] - public ulong NumberULong - { - get => _numberULong; - set - { - _numberULong = value; - OnPropertyChanged(); - } - } - - private ulong? _nullableULong; - [ThingProperty(Enum = new object[]{ null, ulong.MaxValue, ulong.MinValue })] - public ulong? NullableULong - { - get => _nullableULong; - set - { - _nullableULong = value; - OnPropertyChanged(); - } - } - - private double _numberDouble; - [ThingProperty(Enum = new object[]{ 0, double.MaxValue, double.MinValue })] - public double NumberDouble - { - get => _numberDouble; - set - { - _numberDouble = value; - OnPropertyChanged(); - } - } - - private double? _nullableDouble; - [ThingProperty(Enum = new object[]{ null, double.MaxValue, double.MinValue })] - public double? NullableDouble - { - get => _nullableDouble; - set - { - _nullableDouble = value; - OnPropertyChanged(); - } - } - - private float _numberFloat; - [ThingProperty(Enum = new object[]{ 0, float.MaxValue, float.MinValue })] - public float NumberFloat - { - get => _numberFloat; - set - { - _numberFloat = value; - OnPropertyChanged(); - } - } - - private float? _nullableFloat; - [ThingProperty(Enum = new object[]{ null, float.MaxValue, float.MinValue })] - public float? NullableFloat - { - get => _nullableFloat; - set - { - _nullableFloat = value; - OnPropertyChanged(); - } - } - - private decimal _numberDecimal; - [ThingProperty(Enum = new object[]{ 0, 1d, 100 })] - public decimal NumberDecimal - { - get => _numberDecimal; - set - { - _numberDecimal = value; - OnPropertyChanged(); - } - } - - private decimal? _nullableDecimal; - [ThingProperty(Enum = new object[]{ null, 1d, 100 })] - public decimal? NullableDecimal - { - get => _nullableDecimal; - set - { - _nullableDecimal = value; - OnPropertyChanged(); - } - } - - - private string _text; - - [ThingProperty(Enum = new object[]{ null, "ola", "ass", "aaa" })] - public string Text - { - get => _text; - set - { - _text = value; - OnPropertyChanged(); - } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyThing.cs deleted file mode 100644 index d84528c..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyThing.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class PropertyThing : Thing - { - public override string Name => "Property"; - - private bool _on; - - [ThingProperty(Type = new[] {"OnOffProperty"}, Title = "On/Off", Description = "Whether the lamp is turned on")] - public bool On - { - get => _on; - set - { - _on = value; - OnPropertyChanged(); - } - } - - private int _brightness; - - [ThingProperty(Type = new[] {"BrightnessProperty"}, Title = "Brightness", - Description = "The level of light from 0-100", Minimum = 0, Maximum = 100)] - public int Brightness - { - get => _brightness; - set - { - _brightness = value; - OnPropertyChanged(); - } - } - - public int Reader => _brightness; - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyTypeThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyTypeThing.cs deleted file mode 100644 index 65c44a3..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyTypeThing.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class PropertyTypeThing : Thing - { - public override string Name => "property-type"; - - private bool _bool; - public bool Bool - { - get => _bool; - set - { - _bool = value; - OnPropertyChanged(); - } - } - - private bool? _nullableBool; - public bool? NullableBool - { - get => _nullableBool; - set - { - _nullableBool = value; - OnPropertyChanged(); - } - } - - private byte _numberByte; - public byte NumberByte - { - get => _numberByte; - set - { - _numberByte = value; - OnPropertyChanged(); - } - } - - private byte? _nullableByte; - public byte? NullableByte - { - get => _nullableByte; - set - { - _nullableByte = value; - OnPropertyChanged(); - } - } - - private sbyte _numberSByte; - public sbyte NumberSByte - { - get => _numberSByte; - set - { - _numberSByte = value; - OnPropertyChanged(); - } - } - - private sbyte? _nullableSByte; - public sbyte? NullableSByte - { - get => _nullableSByte; - set - { - _nullableSByte = value; - OnPropertyChanged(); - } - } - - private short _numberShort; - public short NumberShort - { - get => _numberShort; - set - { - _numberShort = value; - OnPropertyChanged(); - } - } - - private short? _nullableShort; - public short? NullableShort - { - get => _nullableShort; - set - { - _nullableShort = value; - OnPropertyChanged(); - } - } - - private ushort _numberUShort; - public ushort NumberUShort - { - get => _numberUShort; - set - { - _numberUShort = value; - OnPropertyChanged(); - } - } - - private ushort? _nullableUShort; - public ushort? NullableUShort - { - get => _nullableUShort; - set - { - _nullableUShort = value; - OnPropertyChanged(); - } - } - - private int _numberInt; - public int NumberInt - { - get => _numberInt; - set - { - _numberInt = value; - OnPropertyChanged(); - } - } - - private int? _nullableInt; - public int? NullableInt - { - get => _nullableInt; - set - { - _nullableInt = value; - OnPropertyChanged(); - } - } - - private uint _numberUInt; - public uint NumberUInt - { - get => _numberUInt; - set - { - _numberUInt = value; - OnPropertyChanged(); - } - } - - private uint? _nullableUInt; - public uint? NullableUInt - { - get => _nullableUInt; - set - { - _nullableUInt = value; - OnPropertyChanged(); - } - } - - private long _numberLong; - public long NumberLong - { - get => _numberLong; - set - { - _numberLong = value; - OnPropertyChanged(); - } - } - - private long? _nullableLong; - public long? NullableLong - { - get => _nullableLong; - set - { - _nullableLong = value; - OnPropertyChanged(); - } - } - - private ulong _numberULong; - public ulong NumberULong - { - get => _numberULong; - set - { - _numberULong = value; - OnPropertyChanged(); - } - } - - private ulong? _nullableULong; - public ulong? NullableULong - { - get => _nullableULong; - set - { - _nullableULong = value; - OnPropertyChanged(); - } - } - - private double _numberDouble; - public double NumberDouble - { - get => _numberDouble; - set - { - _numberDouble = value; - OnPropertyChanged(); - } - } - - private double? _nullableDouble; - public double? NullableDouble - { - get => _nullableDouble; - set - { - _nullableDouble = value; - OnPropertyChanged(); - } - } - - private float _numberFloat; - public float NumberFloat - { - get => _numberFloat; - set - { - _numberFloat = value; - OnPropertyChanged(); - } - } - - private float? _nullableFloat; - public float? NullableFloat - { - get => _nullableFloat; - set - { - _nullableFloat = value; - OnPropertyChanged(); - } - } - - private decimal _numberDecimal; - public decimal NumberDecimal - { - get => _numberDecimal; - set - { - _numberDecimal = value; - OnPropertyChanged(); - } - } - - private decimal? _nullableDecimal; - public decimal? NullableDecimal - { - get => _nullableDecimal; - set - { - _nullableDecimal = value; - OnPropertyChanged(); - } - } - - private string _text; - public string Text - { - get => _text; - set - { - _text = value; - OnPropertyChanged(); - } - } - - private DateTime _data; - public DateTime Data - { - get => _data; - set - { - _data = value; - OnPropertyChanged(); - } - } - - private DateTime? _nullableData; - public DateTime? NullableData - { - get => _nullableData; - set - { - _nullableData = value; - OnPropertyChanged(); - } - } - - private DateTimeOffset _dataOffset; - public DateTimeOffset DataOffset - { - get => _dataOffset; - set - { - _dataOffset = value; - OnPropertyChanged(); - } - } - - private DateTimeOffset? _nullableDataOffset; - public DateTimeOffset? NullableDataOffset - { - get => _nullableDataOffset; - set - { - _nullableDataOffset = value; - OnPropertyChanged(); - } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyValidationThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyValidationThing.cs deleted file mode 100644 index 09e0d29..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/PropertyValidationThing.cs +++ /dev/null @@ -1,298 +0,0 @@ -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class PropertyValidationThing : Thing - { - public override string Name => "property-validation-type"; - - private byte _numberByte; - [ThingProperty(Minimum = 1, Maximum = 100)] - public byte NumberByte - { - get => _numberByte; - set - { - _numberByte = value; - OnPropertyChanged(); - } - } - - private byte? _nullableByte; - [ThingProperty(Minimum = 1, Maximum = 100)] - public byte? NullableByte - { - get => _nullableByte; - set - { - _nullableByte = value; - OnPropertyChanged(); - } - } - - private sbyte _numberSByte; - [ThingProperty(Minimum = 1, Maximum = 100)] - public sbyte NumberSByte - { - get => _numberSByte; - set - { - _numberSByte = value; - OnPropertyChanged(); - } - } - - private sbyte? _nullableSByte; - [ThingProperty(Minimum = 1, Maximum = 100)] - public sbyte? NullableSByte - { - get => _nullableSByte; - set - { - _nullableSByte = value; - OnPropertyChanged(); - } - } - - private short _numberShort; - [ThingProperty(Minimum = 1, Maximum = 100)] - public short NumberShort - { - get => _numberShort; - set - { - _numberShort = value; - OnPropertyChanged(); - } - } - - private short? _nullableShort; - [ThingProperty(Minimum = 1, Maximum = 100)] - public short? NullableShort - { - get => _nullableShort; - set - { - _nullableShort = value; - OnPropertyChanged(); - } - } - - private ushort _numberUShort; - [ThingProperty(Minimum = 1, Maximum = 100)] - public ushort NumberUShort - { - get => _numberUShort; - set - { - _numberUShort = value; - OnPropertyChanged(); - } - } - - private ushort? _nullableUShort; - [ThingProperty(Minimum = 1, Maximum = 100)] - public ushort? NullableUShort - { - get => _nullableUShort; - set - { - _nullableUShort = value; - OnPropertyChanged(); - } - } - - private int _numberInt; - [ThingProperty(Minimum = 1, Maximum = 100)] - public int NumberInt - { - get => _numberInt; - set - { - _numberInt = value; - OnPropertyChanged(); - } - } - - private int? _nullableInt; - [ThingProperty(Minimum = 1, Maximum = 100)] - public int? NullableInt - { - get => _nullableInt; - set - { - _nullableInt = value; - OnPropertyChanged(); - } - } - - private uint _numberUInt; - [ThingProperty(Minimum = 1, Maximum = 100)] - public uint NumberUInt - { - get => _numberUInt; - set - { - _numberUInt = value; - OnPropertyChanged(); - } - } - - private uint? _nullableUInt; - [ThingProperty(Minimum = 1, Maximum = 100)] - public uint? NullableUInt - { - get => _nullableUInt; - set - { - _nullableUInt = value; - OnPropertyChanged(); - } - } - - private long _numberLong; - [ThingProperty(Minimum = 1, Maximum = 100)] - public long NumberLong - { - get => _numberLong; - set - { - _numberLong = value; - OnPropertyChanged(); - } - } - - private long? _nullableLong; - [ThingProperty(Minimum = 1, Maximum = 100)] - public long? NullableLong - { - get => _nullableLong; - set - { - _nullableLong = value; - OnPropertyChanged(); - } - } - - private ulong _numberULong; - [ThingProperty(Minimum = 1, Maximum = 100)] - public ulong NumberULong - { - get => _numberULong; - set - { - _numberULong = value; - OnPropertyChanged(); - } - } - - private ulong? _nullableULong; - [ThingProperty(Minimum = 1, Maximum = 100)] - public ulong? NullableULong - { - get => _nullableULong; - set - { - _nullableULong = value; - OnPropertyChanged(); - } - } - - private double _numberDouble; - [ThingProperty(Minimum = 1, Maximum = 100)] - public double NumberDouble - { - get => _numberDouble; - set - { - _numberDouble = value; - OnPropertyChanged(); - } - } - - private double? _nullableDouble; - [ThingProperty(Minimum = 1, Maximum = 100)] - public double? NullableDouble - { - get => _nullableDouble; - set - { - _nullableDouble = value; - OnPropertyChanged(); - } - } - - private float _numberFloat; - [ThingProperty(Minimum = 1, Maximum = 100)] - public float NumberFloat - { - get => _numberFloat; - set - { - _numberFloat = value; - OnPropertyChanged(); - } - } - - private float? _nullableFloat; - [ThingProperty(Minimum = 1, Maximum = 100)] - public float? NullableFloat - { - get => _nullableFloat; - set - { - _nullableFloat = value; - OnPropertyChanged(); - } - } - - private decimal _numberDecimal; - [ThingProperty(Minimum = 1, Maximum = 100)] - public decimal NumberDecimal - { - get => _numberDecimal; - set - { - _numberDecimal = value; - OnPropertyChanged(); - } - } - - private decimal? _nullableDecimal; - [ThingProperty(Minimum = 1, Maximum = 100)] - public decimal? NullableDecimal - { - get => _nullableDecimal; - set - { - _nullableDecimal = value; - OnPropertyChanged(); - } - } - - - private string _text; - [ThingProperty(MinimumLength = 1, MaximumLength = 100)] - public string Text - { - get => _text; - set - { - _text = value; - OnPropertyChanged(); - } - } - - private string _email; - [ThingProperty(Pattern = @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$")] - public string Email - { - get => _email; - set - { - _email = value; - OnPropertyChanged(); - } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyEnumThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyEnumThing.cs deleted file mode 100644 index da77867..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyEnumThing.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class WebSocketPropertyEnumThing : PropertyEnumThing - { - public override string Name => "web-socket-property-enum-type"; - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyThing.cs deleted file mode 100644 index 60050ec..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyThing.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Mozilla.IoT.WebThing.Attributes; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class WebSocketPropertyThing : Thing - { - public override string Name => "web-socket-property"; - - private bool _on; - - [ThingProperty(Type = new[] {"OnOffProperty"}, Title = "On/Off", Description = "Whether the lamp is turned on")] - public bool On - { - get => _on; - set - { - _on = value; - OnPropertyChanged(); - } - } - - private int _brightness; - - [ThingProperty(Type = new[] {"BrightnessProperty"}, Title = "Brightness", - Description = "The level of light from 0-100", Minimum = 0, Maximum = 100)] - public int Brightness - { - get => _brightness; - set - { - _brightness = value; - OnPropertyChanged(); - } - } - - public int Reader => _brightness; - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyTypeThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyTypeThing.cs deleted file mode 100644 index 8ded0b0..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyTypeThing.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class WebSocketPropertyTypeThing : PropertyTypeThing - { - public override string Name => "web-socket-property-type"; - - private bool _bool; - public bool Bool - { - get => _bool; - set - { - _bool = value; - OnPropertyChanged(); - } - } - - private byte _numberByte; - public byte NumberByte - { - get => _numberByte; - set - { - _numberByte = value; - OnPropertyChanged(); - } - } - - private sbyte _numberSByte; - public sbyte NumberSByte - { - get => _numberSByte; - set - { - _numberSByte = value; - OnPropertyChanged(); - } - } - - private short _numberShort; - public short NumberShort - { - get => _numberShort; - set - { - _numberShort = value; - OnPropertyChanged(); - } - } - - private ushort _numberUShort; - public ushort NumberUShort - { - get => _numberUShort; - set - { - _numberUShort = value; - OnPropertyChanged(); - } - } - - private int _numberInt; - public int NumberInt - { - get => _numberInt; - set - { - _numberInt = value; - OnPropertyChanged(); - } - } - - private uint _numberUInt; - public uint NumberUInt - { - get => _numberUInt; - set - { - _numberUInt = value; - OnPropertyChanged(); - } - } - - private long _numberLong; - public long NumberLong - { - get => _numberLong; - set - { - _numberLong = value; - OnPropertyChanged(); - } - } - - private ulong _numberULong; - public ulong NumberULong - { - get => _numberULong; - set - { - _numberULong = value; - OnPropertyChanged(); - } - } - - private double _numberDouble; - public double NumberDouble - { - get => _numberDouble; - set - { - _numberDouble = value; - OnPropertyChanged(); - } - } - - private float _numberFloat; - public float NumberFloat - { - get => _numberFloat; - set - { - _numberFloat = value; - OnPropertyChanged(); - } - } - - private decimal _numberDecimal; - public decimal NumberDecimal - { - get => _numberDecimal; - set - { - _numberDecimal = value; - OnPropertyChanged(); - } - } - - private string _text; - public string Text - { - get => _text; - set - { - _text = value; - OnPropertyChanged(); - } - } - - private DateTime _data; - public DateTime Data - { - get => _data; - set - { - _data = value; - OnPropertyChanged(); - } - } - - private DateTimeOffset _dataOffset; - public DateTimeOffset DataOffset - { - get => _dataOffset; - set - { - _dataOffset = value; - OnPropertyChanged(); - } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyValidationThing.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyValidationThing.cs deleted file mode 100644 index b507a4a..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Things/WebSocketPropertyValidationThing.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Mozilla.IoT.WebThing.AcceptanceTest.Things -{ - public class WebSocketPropertyValidationThing : PropertyValidationThing - { - public override string Name => "web-socket-property-validation-type"; - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Action.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Action.cs deleted file mode 100644 index bb5e61a..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Action.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class Action - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - - [Theory] - [InlineData(50, 2_000)] - public async Task Create(int level, int duration) - { - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""fade"": {{ - ""input"": {{ - ""level"": {level}, - ""duration"": {duration} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(segment.Slice(0, result.Count)), - new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.Fade.Input.Should().NotBeNull(); - json.Data.Fade.Input.Level.Should().Be(level); - json.Data.Fade.Input.Duration.Should().Be(duration); - json.Data.Fade.Href.Should().StartWith("/things/action/actions/fade/"); - json.Data.Fade.Status.Should().Be("pending"); - json.Data.Fade.TimeRequested.Should().BeBefore(DateTime.UtcNow); - json.Data.Fade.TimeCompleted.Should().BeNull(); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - segment = new ArraySegment(new byte[4096]); - result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - json = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(segment.Slice(0, result.Count)), - new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.Fade.Input.Should().NotBeNull(); - json.Data.Fade.Input.Level.Should().Be(level); - json.Data.Fade.Input.Duration.Should().Be(duration); - json.Data.Fade.Href.Should().StartWith("/things/action/actions/fade/"); - json.Data.Fade.Status.Should().Be("executing"); - json.Data.Fade.TimeRequested.Should().BeBefore(DateTime.UtcNow); - json.Data.Fade.TimeCompleted.Should().BeNull(); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - segment = new ArraySegment(new byte[4096]); - result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - json = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(segment.Slice(0, result.Count)), - new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.Fade.Input.Should().NotBeNull(); - json.Data.Fade.Input.Level.Should().Be(level); - json.Data.Fade.Input.Duration.Should().Be(duration); - json.Data.Fade.Href.Should().StartWith("/things/action/actions/fade/"); - json.Data.Fade.Status.Should().Be("completed"); - json.Data.Fade.TimeRequested.Should().BeBefore(DateTime.UtcNow); - json.Data.Fade.TimeCompleted.Should().NotBeNull(); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await client.GetAsync($"/things/action/actions/fade", source.Token) - .ConfigureAwait(false); - var message = await response.Content.ReadAsStringAsync(); - var json2 = JsonConvert.DeserializeObject>(message, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - json2[0].Href.Should().StartWith("/things/action/actions/fade/"); - json2[0].Status.Should().NotBeNullOrEmpty(); - json2[0].Status.Should().Be("completed"); - json2[0].TimeRequested.Should().BeBefore(DateTime.UtcNow); - json2[0].TimeCompleted.Should().NotBeNull(); - json2[0].TimeCompleted.Should().BeBefore(DateTime.UtcNow); - } - - public class Message - { - public string MessageType { get; set; } - public ActionSocket Data { get; set; } - } - - public class ActionSocket - { - public Http.Action.Fade Fade { get; set; } - } - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/ActionType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/ActionType.cs deleted file mode 100644 index ebade6b..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/ActionType.cs +++ /dev/null @@ -1,951 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class ActionType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly Fixture _fixture; - - public ActionType() - { - _fixture = new Fixture(); - } - - [Fact] - public async Task RunAction() - { - var @bool = _fixture.Create(); - var @byte = _fixture.Create(); - var @sbyte = _fixture.Create(); - var @short = _fixture.Create(); - var @ushort = _fixture.Create(); - var @int = _fixture.Create(); - var @uint = _fixture.Create(); - var @long = _fixture.Create(); - var @ulong = _fixture.Create(); - var @double = _fixture.Create(); - var @float = _fixture.Create(); - var @decimal = _fixture.Create(); - var @string = _fixture.Create(); - var @dateTime = _fixture.Create(); - var @dateTimeOffset = _fixture.Create(); - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""run"": {{ - ""input"": {{ - ""bool"": {@bool.ToString().ToLower()}, - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal}, - ""string"": ""{@string}"", - ""dateTime"": ""{@dateTime:O}"", - ""dateTimeOffset"": ""{@dateTimeOffset:O}"" - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(segment.Slice(0, result.Count)), - new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.Run.Input.Bool.Should().Be(@bool); - json.Data.Run.Input.Byte.Should().Be(@byte); - json.Data.Run.Input.Sbyte.Should().Be(@sbyte); - json.Data.Run.Input.Short.Should().Be(@short); - json.Data.Run.Input.UShort.Should().Be(@ushort); - json.Data.Run.Input.Int.Should().Be(@int); - json.Data.Run.Input.Uint.Should().Be(@uint); - json.Data.Run.Input.Long.Should().Be(@long); - json.Data.Run.Input.ULong.Should().Be(@ulong); - json.Data.Run.Input.Double.Should().Be(@double); - json.Data.Run.Input.Float.Should().Be(@float); - json.Data.Run.Input.Decimal.Should().Be(@decimal); - json.Data.Run.Input.String.Should().Be(@string); - json.Data.Run.Input.DateTime.Should().Be(dateTime); - json.Data.Run.Input.DateTimeOffset.Should().Be(dateTimeOffset); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunNullAction(bool isNull) - { - var @bool = isNull ? null : new bool?(_fixture.Create()); - var @byte = isNull ? null : new byte?(_fixture.Create()); - var @sbyte = isNull ? null : new sbyte?(_fixture.Create()); - var @short = isNull ? null : new short?(_fixture.Create()); - var @ushort = isNull ? null : new ushort?(_fixture.Create()); - var @int = isNull ? null : new int?(_fixture.Create()); - var @uint = isNull ? null : new uint?(_fixture.Create()); - var @long = isNull ? null : new long?(_fixture.Create()); - var @ulong = isNull ? null : new ulong?(_fixture.Create()); - var @double = isNull ? null : new double?(_fixture.Create()); - var @float = isNull ? null : new float?(_fixture.Create()); - var @decimal = isNull ? null : new decimal?(_fixture.Create()); - var @string = isNull ? null : _fixture.Create(); - var @dateTime = isNull ? null : new DateTime?(_fixture.Create()); - var @dateTimeOffset = isNull ? null : new DateTimeOffset?(_fixture.Create()); - - var @boolS = isNull ? "null" : @bool.ToString().ToLower(); - var @byteS = isNull ? "null" : @byte.ToString(); - var @sbyteS = isNull ? "null" : @sbyte.ToString(); - var @shortS = isNull ? "null" : @short.ToString(); - var @ushortS = isNull ? "null" : @ushort.ToString(); - var @intS = isNull ? "null" : @int.ToString(); - var @uintS = isNull ? "null" : @uint.ToString(); - var @longS = isNull ? "null" : @long.ToString(); - var @ulongS = isNull ? "null" : @ulong.ToString(); - var @doubleS = isNull ? "null" : @double.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @floatS = isNull ? "null" : @float.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @decimalS = isNull ? "null" : @decimal.GetValueOrDefault().ToString(CultureInfo.InvariantCulture); - var @stringS = isNull ? "null" : $"\"{@string}\""; - var @dateTimeS = isNull ? "null" : $"\"{@dateTime:O}\""; - var @dateTimeOffsetS = isNull ? "null" : $"\"{@dateTimeOffset:O}\""; - - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runNull"": {{ - ""input"": {{ - ""bool"": {@boolS}, - ""byte"": {@byteS}, - ""sbyte"": {@sbyteS}, - ""short"": {@shortS}, - ""ushort"": {@ushortS}, - ""int"": {@intS}, - ""uint"": {@uintS}, - ""long"": {@longS}, - ""ulong"": {@ulongS}, - ""double"": {@doubleS}, - ""float"": {@floatS}, - ""decimal"": {@decimalS}, - ""string"": {@stringS}, - ""dateTime"": {@dateTimeS}, - ""dateTimeOffset"": {@dateTimeOffsetS} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(segment.Slice(0, result.Count)), - new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunNull.Input.Bool.Should().Be(@bool); - json.Data.RunNull.Input.Byte.Should().Be(@byte); - json.Data.RunNull.Input.Sbyte.Should().Be(@sbyte); - json.Data.RunNull.Input.Short.Should().Be(@short); - json.Data.RunNull.Input.UShort.Should().Be(@ushort); - json.Data.RunNull.Input.Int.Should().Be(@int); - json.Data.RunNull.Input.Uint.Should().Be(@uint); - json.Data.RunNull.Input.Long.Should().Be(@long); - json.Data.RunNull.Input.ULong.Should().Be(@ulong); - json.Data.RunNull.Input.Double.Should().Be(@double); - json.Data.RunNull.Input.Float.Should().Be(@float); - json.Data.RunNull.Input.Decimal.Should().Be(@decimal); - json.Data.RunNull.Input.String.Should().Be(@string); - json.Data.RunNull.Input.DateTime.Should().Be(dateTime); - json.Data.RunNull.Input.DateTimeOffset.Should().Be(dateTimeOffset); - } - - [Fact] - public async Task RunWithValidationAction() - { - var @byte = (byte)10; - var @sbyte = (sbyte)10; - var @short = (short)10; - var @ushort = (ushort)10; - var @int = 10; - var @uint = (uint)10; - var @long = (long)10; - var @ulong = (ulong)10; - var @double = (double)10; - var @float = (float)10; - var @decimal = (decimal)10; - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunWithValidation.Input.Byte.Should().Be(@byte); - json.Data.RunWithValidation.Input.Sbyte.Should().Be(@sbyte); - json.Data.RunWithValidation.Input.Short.Should().Be(@short); - json.Data.RunWithValidation.Input.UShort.Should().Be(@ushort); - json.Data.RunWithValidation.Input.Int.Should().Be(@int); - json.Data.RunWithValidation.Input.Uint.Should().Be(@uint); - json.Data.RunWithValidation.Input.Long.Should().Be(@long); - json.Data.RunWithValidation.Input.ULong.Should().Be(@ulong); - json.Data.RunWithValidation.Input.Double.Should().Be(@double); - json.Data.RunWithValidation.Input.Float.Should().Be(@float); - // json.Data.RunNullVWithValidation.Input.Decimal.Should().Be(@decimal); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithValidationWithMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)1 : (byte)100; - var @sbyte = isMin ? (sbyte)1 : (sbyte)100; - var @short = isMin ? (short)1 : (short)100; - var @ushort = isMin ? (ushort)1 : (ushort)100; - var @int = isMin ? (int)1 : 100; - var @uint = isMin ? 1 : (uint)100; - var @long = isMin ? 1 : (long)100; - var @ulong = isMin ? 1 : (ulong)100; - var @double = isMin ? 1 : (double)100; - var @float = isMin ? 1 : (float)100; - var @decimal = isMin ? 1 : (decimal)100; - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunWithValidation.Input.Byte.Should().Be(@byte); - json.Data.RunWithValidation.Input.Sbyte.Should().Be(@sbyte); - json.Data.RunWithValidation.Input.Short.Should().Be(@short); - json.Data.RunWithValidation.Input.UShort.Should().Be(@ushort); - json.Data.RunWithValidation.Input.Int.Should().Be(@int); - json.Data.RunWithValidation.Input.Uint.Should().Be(@uint); - json.Data.RunWithValidation.Input.Long.Should().Be(@long); - json.Data.RunWithValidation.Input.ULong.Should().Be(@ulong); - json.Data.RunWithValidation.Input.Double.Should().Be(@double); - json.Data.RunWithValidation.Input.Float.Should().Be(@float); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithInvalidationWithMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)0 : (byte)101; - var @sbyte = isMin ? (sbyte)0 : (sbyte)101; - var @short = isMin ? (short)0 : (short)101; - var @ushort = isMin ? (ushort)0 : (ushort)101; - var @int = isMin ? 0 : 101; - var @uint = isMin ? 0 : (uint)101; - var @long = isMin ? 0 : (long)101; - var @ulong = isMin ? 0 : (ulong)101; - var @double = isMin ? 0 : (double)101; - var @float = isMin ? 0 : (float)101; - var @decimal = isMin ? 0 : (decimal)101; - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidation"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("error"); - json.Data.Status.Should().Be("400 Bad Request"); - json.Data.Message.Should().Be("Invalid action request"); - } - - - [Fact] - public async Task RunWithValidationExclusive() - { - var @byte = (byte)10; - var @sbyte = (sbyte)10; - var @short = (short)10; - var @ushort = (ushort)10; - var @int = 10; - var @uint = (uint)10; - var @long = (long)10; - var @ulong = (ulong)10; - var @double = (double)10; - var @float = (float)10; - var @decimal = (decimal)10; - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunWithValidationExclusive.Input.Byte.Should().Be(@byte); - json.Data.RunWithValidationExclusive.Input.Sbyte.Should().Be(@sbyte); - json.Data.RunWithValidationExclusive.Input.Short.Should().Be(@short); - json.Data.RunWithValidationExclusive.Input.UShort.Should().Be(@ushort); - json.Data.RunWithValidationExclusive.Input.Int.Should().Be(@int); - json.Data.RunWithValidationExclusive.Input.Uint.Should().Be(@uint); - json.Data.RunWithValidationExclusive.Input.Long.Should().Be(@long); - json.Data.RunWithValidationExclusive.Input.ULong.Should().Be(@ulong); - json.Data.RunWithValidationExclusive.Input.Double.Should().Be(@double); - json.Data.RunWithValidationExclusive.Input.Float.Should().Be(@float); - // json.Data.RunNullVWithValidation.Input.Decimal.Should().Be(@decimal); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithValidationWithExclusiveMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)2 : (byte)99; - var @sbyte = isMin ? (sbyte)2 : (sbyte)99; - var @short = isMin ? (short)2 : (short)99; - var @ushort = isMin ? (ushort)2 : (ushort)99; - var @int = isMin ? (int)2 : 99; - var @uint = isMin ? 2 : (uint)99; - var @long = isMin ? 2 : (long)99; - var @ulong = isMin ? 2 : (ulong)99; - var @double = isMin ? 2 : (double)99; - var @float = isMin ? 2 : (float)99; - var @decimal = isMin ? 2 : (decimal)99; - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunWithValidationExclusive.Input.Byte.Should().Be(@byte); - json.Data.RunWithValidationExclusive.Input.Sbyte.Should().Be(@sbyte); - json.Data.RunWithValidationExclusive.Input.Short.Should().Be(@short); - json.Data.RunWithValidationExclusive.Input.UShort.Should().Be(@ushort); - json.Data.RunWithValidationExclusive.Input.Int.Should().Be(@int); - json.Data.RunWithValidationExclusive.Input.Uint.Should().Be(@uint); - json.Data.RunWithValidationExclusive.Input.Long.Should().Be(@long); - json.Data.RunWithValidationExclusive.Input.ULong.Should().Be(@ulong); - json.Data.RunWithValidationExclusive.Input.Double.Should().Be(@double); - json.Data.RunWithValidationExclusive.Input.Float.Should().Be(@float); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task RunWithInvalidationWithExclusiveMinAndMax(bool isMin) - { - var @byte = isMin ? (byte)1 : (byte)100; - var @sbyte = isMin ? (sbyte)1 : (sbyte)100; - var @short = isMin ? (short)1 : (short)100; - var @ushort = isMin ? (ushort)1 : (ushort)100; - var @int = isMin ? 1 : 100; - var @uint = isMin ? 1 : (uint)100; - var @long = isMin ? 1 : (long)100; - var @ulong = isMin ? 1 : (ulong)100; - var @double = isMin ? 1 : (double)100; - var @float = isMin ? 1 : (float)100; - var @decimal = isMin ? 1 : (decimal)100; - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithValidationExclusive"": {{ - ""input"": {{ - ""byte"": {@byte}, - ""sbyte"": {@sbyte}, - ""short"": {@short}, - ""ushort"": {@ushort}, - ""int"": {@int}, - ""uint"": {@uint}, - ""long"": {@long}, - ""ulong"": {@ulong}, - ""double"": {@double}, - ""float"": {@float}, - ""decimal"": {@decimal} - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("error"); - json.Data.Status.Should().Be("400 Bad Request"); - json.Data.Message.Should().Be("Invalid action request"); - } - - [Theory] - [InlineData("a")] - [InlineData("abc")] - [InlineData("0123456789")] - public async Task RunWithStringValidationValid(string min) - { - var email = "test@gmail.com"; - - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithStringValidation"": {{ - ""input"": {{ - ""minAnMax"": ""{min}"", - ""mail"": ""{email}"" - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("actionStatus"); - json.Data.RunWithStringValidation.Input.MinAnMax.Should().Be(min); - json.Data.RunWithStringValidation.Input.Mail.Should().Be(email); - } - - - [Theory] - [InlineData(null, "test@tese.com")] - [InlineData("", "test@tese.com")] - [InlineData("a0123456789", "test@tese.com")] - [InlineData("abc", null)] - [InlineData("abc", "test")] - public async Task RunWithStringValidationInvalid(string min, string email) - { - - var host = await Program.CreateHostBuilder(null) - .StartAsync() - .ConfigureAwait(false); - var client = host.GetTestServer().CreateClient(); - var webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - var uri = new UriBuilder(client.BaseAddress) - { - Scheme = "ws", - Path = "/things/action-type" - }.Uri; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await webSocketClient.ConnectAsync(uri, source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""requestAction"", - ""data"": {{ - ""runWithStringValidation"": {{ - ""input"": {{ - ""minAnMax"": ""{min}"", - ""mail"": ""{email}"" - }} - }} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var message = Encoding.UTF8.GetString(segment.Slice(0, result.Count)); - var json = JsonConvert.DeserializeObject(message, new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()}); - - json.MessageType.Should().Be("error"); - json.Data.Status.Should().Be("400 Bad Request"); - json.Data.Message.Should().Be("Invalid action request"); - } - - - public class Message - { - public string MessageType { get; set; } - public ActionSocket Data { get; set; } - } - - public class ActionSocket - { - public string Status { get; set; } - public string Message { get; set; } - public Http.ActionType.Run Run { get; set; } - public Http.ActionType.RunNull RunNull { get; set; } - - public Http.ActionType.Run RunWithValidation { get; set; } - public Http.ActionType.Run RunWithValidationExclusive { get; set; } - - - public Http.ActionType.RunString RunWithStringValidation { get; set; } - } - - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Event.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Event.cs deleted file mode 100644 index df7bba4..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Event.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class Event - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly WebSocketClient _webSocketClient; - private readonly HttpClient _client; - private readonly Uri _uri; - - public Event() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - _webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - _uri = new UriBuilder(_client.BaseAddress) {Scheme = "ws", Path = "/things/event"}.Uri; - } - - [Theory] - [InlineData("overheated")] - public async Task EventSubscription(string @event) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""addEventSubscription"", - ""data"": {{ - ""{@event}"": {{}} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment,source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - json.Type.Should().Be(JTokenType.Object); - - var obj = (JObject)json; - - obj.GetValue("messageType", StringComparison.OrdinalIgnoreCase).Type.Should() - .Be(JTokenType.String); - obj.GetValue("messageType", StringComparison.OrdinalIgnoreCase).Value().Should() - .Be("event"); - - ((JObject)obj.GetValue("data", StringComparison.OrdinalIgnoreCase)) - .GetValue("overheated", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Object); - - - var overheated = ((JObject)((JObject)obj.GetValue("data", StringComparison.OrdinalIgnoreCase)) - .GetValue("overheated", StringComparison.OrdinalIgnoreCase)); - - overheated - .GetValue("data", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Integer); - - overheated - .GetValue("timestamp", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Date); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/event/events/{@event}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync() - .ConfigureAwait(false); - - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Array); - ((JArray)json).Should().HaveCountGreaterOrEqualTo(1); - - obj = ((JArray)json).Last() as JObject; - obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Object); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Integer); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("data", StringComparison.OrdinalIgnoreCase).Value().Should().Be(0); - - ((JObject)obj.GetValue("overheated", StringComparison.OrdinalIgnoreCase)) - .GetValue("timestamp", StringComparison.OrdinalIgnoreCase).Type.Should().Be(JTokenType.Date); - - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Property.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Property.cs deleted file mode 100644 index 100ce3a..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/Property.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class Property - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly WebSocketClient _webSocketClient; - private readonly HttpClient _client; - private readonly Uri _uri; - public Property() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - _webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - _uri = new UriBuilder(_client.BaseAddress) - { - Scheme = "ws", - Path = "/things/web-socket-property" - }.Uri; - } - - [Theory] - [InlineData("on", true)] - [InlineData("brightness", 10)] - public async Task SetProperties(string property, object value) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value.ToString().ToLower()} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value.ToString().ToLower()} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value.ToString().ToLower()} }}")); - } - - [Theory] - [InlineData("brightness", -1, 0, "Invalid property value")] - [InlineData("brightness", 101, 0, "Invalid property value")] - [InlineData("reader", 50, 0, "Read-only property")] - public async Task SetPropertiesInvalidValue(string property, object value, object defaultValue, string errorMessage) - { - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value.ToString().ToLower()} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""error"", - ""data"": {{ - ""message"": ""{errorMessage}"", - ""status"": ""400 Bad Request"" - }} -}}")); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property/properties/{property}", source.Token) - .ConfigureAwait(false); - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {defaultValue.ToString().ToLower()} }}")); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyEnumType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyEnumType.cs deleted file mode 100644 index cde9c6d..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyEnumType.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class PropertyEnumType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly WebSocketClient _webSocketClient; - private readonly HttpClient _client; - private readonly Uri _uri; - - public PropertyEnumType() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - _webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - _uri = new UriBuilder(_client.BaseAddress) - { - Scheme = "ws", - Path = "/things/web-socket-property-enum-type" - }.Uri; - } - - [Theory] - [InlineData("numberByte", 0)] - [InlineData("numberByte", byte.MaxValue)] - [InlineData("numberByte", byte.MinValue)] - [InlineData("numberSByte", 0)] - [InlineData("numberSByte", sbyte.MaxValue)] - [InlineData("numberSByte", sbyte.MinValue)] - [InlineData("numberShort", 0)] - [InlineData("numberShort", short.MaxValue)] - [InlineData("numberShort", short.MinValue)] - [InlineData("numberUShort", 0)] - [InlineData("numberUShort", ushort.MaxValue)] - [InlineData("numberUShort", ushort.MinValue)] - [InlineData("numberInt", 0)] - [InlineData("numberInt", int.MaxValue)] - [InlineData("numberInt", int.MinValue)] - [InlineData("numberUInt", 0)] - [InlineData("numberUInt", uint.MaxValue)] - [InlineData("numberUInt", uint.MinValue)] - [InlineData("numberLong", 0)] - [InlineData("numberLong", long.MaxValue)] - [InlineData("numberLong", long.MinValue)] - [InlineData("numberULong", 0)] - [InlineData("numberULong", ulong.MaxValue)] - [InlineData("numberULong", ulong.MinValue)] - [InlineData("numberDouble", 0)] - [InlineData("numberDouble", double.MaxValue)] - [InlineData("numberDouble", double.MinValue)] - [InlineData("numberFloat", 0)] - [InlineData("numberFloat", float.MaxValue)] - [InlineData("numberFloat", float.MinValue)] - [InlineData("bool", true)] - [InlineData("bool", false)] - [InlineData("nullableBool", null)] - [InlineData("nullableBool", true)] - [InlineData("nullableBool", false)] - [InlineData("nullableByte", null)] - [InlineData("nullableByte", byte.MaxValue)] - [InlineData("nullableByte", byte.MinValue)] - [InlineData("nullableSByte", null)] - [InlineData("nullableSByte", sbyte.MinValue)] - [InlineData("nullableSByte", sbyte.MaxValue)] - [InlineData("nullableShort", null)] - [InlineData("nullableShort", short.MinValue)] - [InlineData("nullableShort", short.MaxValue)] - [InlineData("nullableUShort", null)] - [InlineData("nullableUShort", ushort.MinValue)] - [InlineData("nullableUShort", ushort.MaxValue)] - [InlineData("nullableInt", null)] - [InlineData("nullableInt", int.MinValue)] - [InlineData("nullableInt", int.MaxValue)] - [InlineData("nullableUInt", null)] - [InlineData("nullableUInt", uint.MinValue)] - [InlineData("nullableUInt", uint.MaxValue)] - [InlineData("nullableLong", null)] - [InlineData("nullableLong", long.MinValue)] - [InlineData("nullableLong", long.MaxValue)] - [InlineData("nullableULong", null)] - [InlineData("nullableULong", ulong.MinValue)] - [InlineData("nullableULong", ulong.MaxValue)] - [InlineData("nullableDouble", null)] - [InlineData("nullableDouble", double.MinValue)] - [InlineData("nullableDouble", double.MaxValue)] - [InlineData("nullableFloat", null)] - [InlineData("nullableFloat", float.MinValue)] - [InlineData("nullableFloat", float.MaxValue)] - [InlineData("nullableDecimal", null)] - public async Task SetProperties(string property, object value) - { - value = value != null ? value.ToString().ToLower() : "null"; - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property-enum-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - - [Theory] - [InlineData("text", "ola")] - [InlineData("text", "ass")] - [InlineData("text", "aaa")] - [InlineData("text", null)] - public async Task SetStringValue(string property, string value) - { - value = value != null ? $"\"{value}\"" : "null"; - - value = value.ToString().ToLower(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property-enum-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyType.cs deleted file mode 100644 index 844b444..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyType.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class PropertyType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly Fixture _fixture; - private readonly WebSocketClient _webSocketClient; - private readonly HttpClient _client; - private readonly Uri _uri; - - public PropertyType() - { - _fixture = new Fixture(); - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - _webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - _uri = new UriBuilder(_client.BaseAddress) - { - Scheme = "ws", - Path = "/things/web-socket-property-type" - }.Uri; - } - - [Theory] - [InlineData("numberByte", typeof(byte))] - [InlineData("numberSByte", typeof(sbyte))] - [InlineData("numberShort", typeof(short))] - [InlineData("numberUShort", typeof(ushort))] - [InlineData("numberInt", typeof(int))] - [InlineData("numberUInt", typeof(uint))] - [InlineData("numberLong", typeof(long))] - [InlineData("numberULong", typeof(ulong))] - [InlineData("numberDouble", typeof(double))] - [InlineData("numberFloat", typeof(float))] - [InlineData("numberDecimal", typeof(decimal))] - [InlineData("bool", typeof(bool))] - [InlineData("nullableBool", typeof(bool?))] - [InlineData("nullableByte", typeof(byte?))] - [InlineData("nullableSByte", typeof(sbyte?))] - [InlineData("nullableShort", typeof(short?))] - [InlineData("nullableUShort", typeof(ushort?))] - [InlineData("nullableInt", typeof(int?))] - [InlineData("nullableUInt", typeof(uint?))] - [InlineData("nullableLong", typeof(long?))] - [InlineData("nullableULong", typeof(ulong?))] - [InlineData("nullableDouble", typeof(double?))] - [InlineData("nullableFloat", typeof(float?))] - [InlineData("nullableDecimal", typeof(decimal?))] - public async Task SetProperties(string property, Type type) - { - var value = CreateValue(type)?.ToString().ToLower(); - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value?.ToString().ToLower()} }}")); - } - - - [Theory] - [InlineData("data", typeof(DateTime))] - [InlineData("dataOffset", typeof(DateTimeOffset))] - [InlineData("nullableData", typeof(DateTime?))] - [InlineData("nullableDataOffset", typeof(DateTimeOffset?))] - [InlineData("text", typeof(string))] - public async Task SetPropertiesStringValue(string property, Type type) - { - var value = CreateValue(type); - - if (value != null && (type == typeof(DateTime) - || type == typeof(DateTime?))) - { - value = ((DateTime)value).ToString("O"); - } - - if (value != null && (type == typeof(DateTimeOffset) - || type == typeof(DateTimeOffset?))) - { - value = ((DateTimeOffset)value).ToString("O"); - } - - value = value != null ? $"\"{value}\"" : "null"; - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - - private object CreateValue(Type type) - { - if (type == typeof(bool)) - { - return _fixture.Create(); - } - - if (type == typeof(bool?)) - { - return _fixture.Create(); - } - - if (type == typeof(byte)) - { - return _fixture.Create(); - } - - if (type == typeof(byte?)) - { - return _fixture.Create(); - } - - if (type == typeof(sbyte)) - { - return _fixture.Create(); - } - - if (type == typeof(sbyte?)) - { - return _fixture.Create(); - } - - if (type == typeof(short)) - { - return _fixture.Create(); - } - - if (type == typeof(short?)) - { - return _fixture.Create(); - } - - if (type == typeof(ushort)) - { - return _fixture.Create(); - } - - if (type == typeof(ushort?)) - { - return _fixture.Create(); - } - - if (type == typeof(int)) - { - return _fixture.Create(); - } - - if (type == typeof(int?)) - { - return _fixture.Create(); - } - - if (type == typeof(uint)) - { - return _fixture.Create(); - } - - if (type == typeof(uint?)) - { - return _fixture.Create(); - } - - if (type == typeof(long)) - { - return _fixture.Create(); - } - - if (type == typeof(long?)) - { - return _fixture.Create(); - } - - if (type == typeof(ulong)) - { - return _fixture.Create(); - } - - if (type == typeof(ulong?)) - { - return _fixture.Create(); - } - - if (type == typeof(double)) - { - return _fixture.Create(); - } - - if (type == typeof(double?)) - { - return _fixture.Create(); - } - - if (type == typeof(float)) - { - return _fixture.Create(); - } - - if (type == typeof(float?)) - { - return _fixture.Create(); - } - - if (type == typeof(decimal)) - { - return _fixture.Create(); - } - - if (type == typeof(decimal?)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTime)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTime?)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTimeOffset)) - { - return _fixture.Create(); - } - - if (type == typeof(DateTimeOffset?)) - { - return _fixture.Create(); - } - - return _fixture.Create(); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyValidationType.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyValidationType.cs deleted file mode 100644 index 24b43c3..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/PropertyValidationType.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class PropertyValidationType - { - private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); - private readonly WebSocketClient _webSocketClient; - private readonly HttpClient _client; - private readonly Uri _uri; - - public PropertyValidationType() - { - var host = Program.GetHost().GetAwaiter().GetResult(); - _client = host.GetTestServer().CreateClient(); - _webSocketClient = host.GetTestServer().CreateWebSocketClient(); - - _uri = new UriBuilder(_client.BaseAddress) - { - Scheme = "ws", - Path = "/things/web-socket-property-validation-type" - }.Uri; - } - - [Theory] - [InlineData("numberByte", 1)] - [InlineData("numberByte", 10)] - [InlineData("numberByte", 100)] - [InlineData("numberSByte", 1)] - [InlineData("numberSByte", 10)] - [InlineData("numberSByte", 100)] - [InlineData("numberShort", 1)] - [InlineData("numberShort", 10)] - [InlineData("numberShort", 100)] - [InlineData("numberUShort", 1)] - [InlineData("numberUShort", 10)] - [InlineData("numberUShort", 100)] - [InlineData("numberInt", 1)] - [InlineData("numberInt", 10)] - [InlineData("numberInt", 100)] - [InlineData("numberUInt", 1)] - [InlineData("numberUInt", 10)] - [InlineData("numberUInt", 100)] - [InlineData("numberLong", 1)] - [InlineData("numberLong", 10)] - [InlineData("numberLong", 100)] - [InlineData("numberULong", 1)] - [InlineData("numberULong", 10)] - [InlineData("numberULong", 100)] - [InlineData("numberDouble", 1)] - [InlineData("numberDouble", 10)] - [InlineData("numberDouble", 100)] - [InlineData("numberFloat", 1)] - [InlineData("numberFloat", 10)] - [InlineData("numberFloat", 100)] - [InlineData("numberDecimal", 1)] - [InlineData("numberDecimal", 10)] - [InlineData("numberDecimal", 100)] - [InlineData("nullableByte", 1)] - [InlineData("nullableByte", 10)] - [InlineData("nullableByte", 100)] - [InlineData("nullableSByte", 1)] - [InlineData("nullableSByte", 10)] - [InlineData("nullableSByte", 100)] - [InlineData("nullableShort", 1)] - [InlineData("nullableShort", 10)] - [InlineData("nullableShort", 100)] - [InlineData("nullableUShort", 1)] - [InlineData("nullableUShort", 10)] - [InlineData("nullableUShort", 100)] - [InlineData("nullableInt", 1)] - [InlineData("nullableInt", 10)] - [InlineData("nullableInt", 100)] - [InlineData("nullableUInt", 1)] - [InlineData("nullableUInt", 10)] - [InlineData("nullableUInt", 100)] - [InlineData("nullableLong", 1)] - [InlineData("nullableLong", 10)] - [InlineData("nullableLong", 100)] - [InlineData("nullableULong", 1)] - [InlineData("nullableULong", 10)] - [InlineData("nullableULong", 100)] - [InlineData("nullableDouble", 1)] - [InlineData("nullableDouble", 10)] - [InlineData("nullableDouble", 100)] - [InlineData("nullableFloat", 1)] - [InlineData("nullableFloat", 10)] - [InlineData("nullableFloat", 100)] - [InlineData("nullableDecimal", 1)] - [InlineData("nullableDecimal", 10)] - [InlineData("nullableDecimal", 100)] - [InlineData("text", "abc")] - [InlineData("email", "text@test.com")] - public async Task SetProperties(string property, object value) - { - if (value is string || value == null) - { - value = value != null ? $"\"{value}\"" : "null"; - } - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""propertyStatus"", - ""data"": {{ - ""{property}"": {value} - }} -}}")); - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var response = await _client.GetAsync($"/things/web-socket-property-validation-type/properties/{property}", source.Token) - .ConfigureAwait(false); - - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Content.Headers.ContentType.ToString().Should().Be( "application/json"); - - var message = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - json = JToken.Parse(message); - - json.Type.Should().Be(JTokenType.Object); - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@"{{ ""{property}"": {value} }}")); - } - - [Theory] - [InlineData("numberByte", 0)] - [InlineData("numberByte", 101)] - [InlineData("numberSByte", 0)] - [InlineData("numberSByte", 101)] - [InlineData("numberShort", 0)] - [InlineData("numberShort", 101)] - [InlineData("numberUShort", 0)] - [InlineData("numberUShort", 101)] - [InlineData("numberInt", 0)] - [InlineData("numberInt", 101)] - [InlineData("numberUInt", 0)] - [InlineData("numberUInt", 101)] - [InlineData("numberLong", 0)] - [InlineData("numberLong", 101)] - [InlineData("numberULong", 0)] - [InlineData("numberULong", 101)] - [InlineData("numberDouble", 0)] - [InlineData("numberDouble", 101)] - [InlineData("numberFloat", 0)] - [InlineData("numberFloat", 101)] - [InlineData("numberDecimal", 0)] - [InlineData("numberDecimal", 101)] - [InlineData("nullableByte", 0)] - [InlineData("nullableByte", 101)] - [InlineData("nullableSByte", 0)] - [InlineData("nullableSByte", 101)] - [InlineData("nullableShort", 0)] - [InlineData("nullableShort", 101)] - [InlineData("nullableUShort", 0)] - [InlineData("nullableUShort", 101)] - [InlineData("nullableInt", 0)] - [InlineData("nullableInt", 101)] - [InlineData("nullableUInt", 0)] - [InlineData("nullableUInt", 101)] - [InlineData("nullableLong", 0)] - [InlineData("nullableLong", 101)] - [InlineData("nullableULong", 0)] - [InlineData("nullableULong", 101)] - [InlineData("nullableDouble", 0)] - [InlineData("nullableDouble", 101)] - [InlineData("nullableFloat", 0)] - [InlineData("nullableFloat", 101)] - [InlineData("nullableDecimal", 0)] - [InlineData("nullableDecimal", 101)] - [InlineData("text", "")] - [InlineData("text", null)] - [InlineData("email", "text")] - [InlineData("email", null)] - public async Task SetPropertiesInvalidNumber(string property, object value) - { - if (value is string || value == null) - { - value = value != null ? $"\"{value}\"" : "null"; - } - - var source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - var socket = await _webSocketClient.ConnectAsync(_uri, source.Token) - .ConfigureAwait(false); - - source = new CancellationTokenSource(); - source.CancelAfter(s_timeout); - - await socket - .SendAsync(Encoding.UTF8.GetBytes($@" -{{ - ""messageType"": ""setProperty"", - ""data"": {{ - ""{property}"": {value} - }} -}}"), WebSocketMessageType.Text, true, - source.Token) - .ConfigureAwait(false); - - - var segment = new ArraySegment(new byte[4096]); - var result = await socket.ReceiveAsync(segment, source.Token) - .ConfigureAwait(false); - - result.MessageType.Should().Be(WebSocketMessageType.Text); - result.EndOfMessage.Should().BeTrue(); - result.CloseStatus.Should().BeNull(); - - var json = JToken.Parse(Encoding.UTF8.GetString(segment.Slice(0, result.Count))); - - FluentAssertions.Json.JsonAssertionExtensions - .Should(json) - .BeEquivalentTo(JToken.Parse($@" -{{ - ""messageType"": ""error"", - ""data"": {{ - ""message"": ""Invalid property value"", - ""status"": ""400 Bad Request"" - }} -}}")); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/WebSocketBody.cs b/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/WebSocketBody.cs deleted file mode 100644 index 3dca430..0000000 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/WebScokets/WebSocketBody.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mozilla.IoT.WebThing.AcceptanceTest.WebScokets -{ - public class WebSocketBody - { - public string MessageType { get; set; } - public object Data { get; set; } - } -} diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/EventTest.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/EventTest.cs new file mode 100644 index 0000000..d203ed8 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/EventTest.cs @@ -0,0 +1,137 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Extensions; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Factories +{ + public class EventTest : IThingContextFactoryTest + { + [Fact] + public void CreateWithEventThing() + { + var thing = new EventThing(); + var context = Factory.Create(thing, new ThingOption + { + MaxEventSize = 2 + }); + + thing.ThingContext = context; + + context.Actions.Should().BeEmpty(); + context.Properties.Should().BeEmpty(); + + context.Events.Should().NotBeEmpty(); + context.Events.Should().HaveCount(2); + context.Events.Should().NotContainKey(nameof(EventThing.Ignore)); + context.Events.Should().NotContainKey(nameof(EventThing.Ignore2)); + context.Events.Should().NotContainKey(nameof(EventThing.Ignore3)); + context.Events.Should().ContainKey(nameof(EventThing.Int)); + context.Events[nameof(EventThing.Int)].Should().NotBeNull(); + context.Events[nameof(EventThing.Int)].ToArray().Should().BeEmpty(); + context.Events.Should().ContainKey("other"); + context.Events["other"].Should().NotBeNull(); + context.Events["other"].ToArray().Should().BeEmpty(); + + var @int = Fixture.Create(); + thing.Invoke(@int); + + var array = context.Events[nameof(EventThing.Int)].ToArray(); + array.Should().NotBeEmpty(); + array.Should().HaveCount(1); + array[0].Data.Should().Be(@int); + + var @string = Fixture.Create(); + thing.Invoke(@string); + + array = context.Events["other"].ToArray(); + array.Should().NotBeEmpty(); + array.Should().HaveCount(1); + array[0].Data.Should().Be(@string); + + @int = Fixture.Create(); + thing.Invoke(@int); + var @int2 = Fixture.Create(); + thing.Invoke(@int2); + + array = context.Events[nameof(EventThing.Int)].ToArray(); + array.Should().NotBeEmpty(); + array.Should().HaveCount(2); + array[0].Data.Should().Be(@int); + array[1].Data.Should().Be(@int2); + + var message = JsonSerializer.Serialize(context.Response, + new ThingOption().ToJsonSerializerOptions()); + + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""events"": { + ""int"": { + ""link"": [ + { + ""href"": ""/things/event-thing/events/int"", + ""rel"": ""event"" + } + ] + }, + ""other"": { + ""title"": ""Title other"", + ""description"": ""Other Description"", + ""unit"": ""Something"", + ""link"": [ + { + ""href"": ""/things/event-thing/events/other"", + ""rel"": ""event"" + } + ] + } + }, + ""links"": [ + { + ""href"": ""properties"", + ""rel"": ""/things/event-thing/properties"" + }, + { + ""href"": ""events"", + ""rel"": ""/things/event-thing/events"" + }, + { + ""href"": ""actions"", + ""rel"": ""/things/event-thing/actions"" + } + ] +} +")); + } + + public class EventThing : Thing + { + public delegate void Info(); + public override string Name => "event-thing"; + + [ThingEvent(Ignore = true)] + public event EventHandler Ignore; + + public event Info Ignore2; + + internal event EventHandler Ignore3; + + public event EventHandler Int; + + [ThingEvent(Name = "Other", Title = "Title other", Description = "Other Description", Unit = "Something")] + public event EventHandler Something; + + internal void Invoke(int value) + => Int?.Invoke(this, value); + + internal void Invoke(string value) + => Something?.Invoke(this, value); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/IThingContextFactoryTest.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/IThingContextFactoryTest.cs new file mode 100644 index 0000000..2ad82e3 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/IThingContextFactoryTest.cs @@ -0,0 +1,21 @@ +using AutoFixture; +using Microsoft.Extensions.DependencyInjection; +using Mozilla.IoT.WebThing.Factories; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Factories +{ + public class IThingContextFactoryTest + { + protected IThingContextFactory Factory { get; } + protected Fixture Fixture { get; } + + public IThingContextFactoryTest() + { + var collection = new ServiceCollection(); + collection.AddThings(); + var provider = collection.BuildServiceProvider(); + Factory = provider.GetRequiredService(); + Fixture = new Fixture(); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/PropertyTest.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/PropertyTest.cs new file mode 100644 index 0000000..5a9e91b --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Factories/PropertyTest.cs @@ -0,0 +1,786 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Properties; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Factories +{ + public class PropertyTest : IThingContextFactoryTest + { + + #region Response + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(Guid))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(char))] + [InlineData(typeof(Foo))] + [InlineData(typeof(string))] + [InlineData(typeof(byte))] + [InlineData(typeof(sbyte))] + [InlineData(typeof(short))] + [InlineData(typeof(ushort))] + [InlineData(typeof(int))] + [InlineData(typeof(uint))] + [InlineData(typeof(long))] + [InlineData(typeof(ulong))] + [InlineData(typeof(float))] + [InlineData(typeof(double))] + [InlineData(typeof(decimal))] + public void ResponseProperty(Type type) + { + if(type == typeof(bool)) + { + TestResponseStructProperty("boolean"); + return; + } + + #region String + + if(type == typeof(Guid)) + { + + TestResponseStructProperty("string"); + return; + } + + if(type == typeof(DateTime)) + { + TestResponseStructProperty("string"); + return; + } + + if(type == typeof(DateTimeOffset)) + { + TestResponseStructProperty("string"); + return; + } + + if(type == typeof(TimeSpan)) + { + TestResponseStructProperty("string"); + return; + } + + if(type == typeof(char)) + { + TestResponseStructProperty("string"); + return; + } + + if(type == typeof(string)) + { + TestResponseNullableProperty("string"); + return; + } + + if(type == typeof(string)) + { + TestResponseNullableProperty("string"); + return; + } + + #endregion + + #region Integer + + if(type == typeof(byte)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(sbyte)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(short)) + { + TestResponseStructProperty("integer");; + return; + } + + if(type == typeof(ushort)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(int)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(uint)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(long)) + { + TestResponseStructProperty("integer"); + return; + } + + if(type == typeof(long)) + { + TestResponseStructProperty("integer"); + return; + } + + #endregion + + #region Number + + if(type == typeof(float)) + { + TestResponseStructProperty("number"); + return; + } + + if(type == typeof(double)) + { + TestResponseStructProperty("number"); + return; + } + + if(type == typeof(decimal)) + { + TestResponseStructProperty("number"); + return; + } + + #endregion + } + + private void TestResponseStructProperty(string type) + where T : struct + { + TestResponseProperty>(type, string.Format(RESPONSE_WITH_NULLABLE, type, + typeof(T).IsEnum ? $@" ""enums"": [""{string.Join(@""" , """, typeof(T).GetEnumNames()) }""] " : string.Empty)); + } + + private void TestResponseNullableProperty(string type) + => TestResponseProperty>(type, string.Format(RESPONSE_WITHOUT_NULLABLE, type, string.Empty)); + + private void TestResponseProperty(string type, string response) + where T : Thing, new() + { + var thing = new T(); + var context = Factory.Create(thing, new ThingOption()); + + var message = JsonSerializer.Serialize(context.Response, + new ThingOption().ToJsonSerializerOptions()); + + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(response)); + } + + #endregion + + #region Valid Property + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(Guid))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(Foo))] + [InlineData(typeof(char))] + [InlineData(typeof(string))] + [InlineData(typeof(byte))] + [InlineData(typeof(sbyte))] + [InlineData(typeof(short))] + [InlineData(typeof(ushort))] + [InlineData(typeof(int))] + [InlineData(typeof(uint))] + [InlineData(typeof(long))] + [InlineData(typeof(ulong))] + [InlineData(typeof(float))] + [InlineData(typeof(double))] + [InlineData(typeof(decimal))] + public void ValidProperty(Type type) + { + if(type == typeof(bool)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x.ToString().ToLower()} }}") + .GetProperty("input")); + return; + } + + #region String + + if(type == typeof(Guid)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(DateTime)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x:O}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(DateTimeOffset)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x:O}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(TimeSpan)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(char)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(Foo)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(string)) + { + TestValidNullableProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": ""{x}"" }}") + .GetProperty("input")); + return; + } + + #endregion + + #region Integer + + if(type == typeof(byte)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(sbyte)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(short)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(ushort)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(int)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(uint)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(long)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(long)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + #endregion + + #region Number + + if(type == typeof(float)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(double)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(decimal)) + { + TestValidProperty(x => + JsonSerializer.Deserialize($@"{{ ""input"": {x} }}") + .GetProperty("input")); + return; + } + + #endregion + } + + + private void TestValidProperty(Func createJsonElement) + where T : struct + { + var thing = new StructPropertyThing(); + var context = Factory.Create(thing, new ThingOption()); + + thing.ThingContext = context; + + context.Actions.Should().BeEmpty(); + context.Events.Should().BeEmpty(); + + context.Properties.Should().NotBeEmpty(); + context.Properties.Should().HaveCount(2); + context.Properties.Should().ContainKey(nameof(StructPropertyThing.Value)); + context.Properties.Should().ContainKey(nameof(StructPropertyThing.NullableValue)); + + var value = Fixture.Create(); + var jsonElement = createJsonElement(value); + + context.Properties[nameof(StructPropertyThing.Value)].SetValue(jsonElement).Should().Be(SetPropertyResult.Ok); + thing.Value.Should().Be(value); + context.Properties[nameof(StructPropertyThing.Value)].GetValue().Should().Be(value); + + context.Properties[nameof(StructPropertyThing.NullableValue)].SetValue(jsonElement).Should().Be(SetPropertyResult.Ok); + thing.NullableValue.Should().Be(value); + context.Properties[nameof(StructPropertyThing.NullableValue)].GetValue().Should().Be(value); + + jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }").GetProperty("input"); + context.Properties[nameof(StructPropertyThing.NullableValue)].SetValue(jsonElement).Should().Be(SetPropertyResult.Ok); + thing.NullableValue.Should().BeNull(); + context.Properties[nameof(StructPropertyThing.NullableValue)].GetValue().Should().BeNull(); + } + + private void TestValidNullableProperty(Func createJsonElement) + { + var thing = new NullablePropertyThing(); + var context = Factory.Create(thing, new ThingOption()); + + thing.ThingContext = context; + + context.Actions.Should().BeEmpty(); + context.Events.Should().BeEmpty(); + + context.Properties.Should().NotBeEmpty(); + context.Properties.Should().HaveCount(1); + context.Properties.Should().ContainKey(nameof(NullablePropertyThing.Value)); + + var value = Fixture.Create(); + var jsonElement = createJsonElement(value); + + context.Properties[nameof(NullablePropertyThing.Value)].SetValue(jsonElement).Should().Be(SetPropertyResult.Ok); + thing.Value.Should().Be(value); + context.Properties[nameof(NullablePropertyThing.Value)].GetValue().Should().Be(value); + + jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }").GetProperty("input"); + context.Properties[nameof(NullablePropertyThing.Value)].SetValue(jsonElement).Should().Be(SetPropertyResult.Ok); + thing.Value.Should().BeNull(); + context.Properties[nameof(NullablePropertyThing.Value)].GetValue().Should().BeNull(); + } + + #endregion + + #region Invalid Property + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(Guid))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(Foo))] + [InlineData(typeof(char))] + [InlineData(typeof(string))] + [InlineData(typeof(byte))] + [InlineData(typeof(sbyte))] + [InlineData(typeof(short))] + [InlineData(typeof(ushort))] + [InlineData(typeof(int))] + [InlineData(typeof(uint))] + [InlineData(typeof(long))] + [InlineData(typeof(ulong))] + [InlineData(typeof(float))] + [InlineData(typeof(double))] + [InlineData(typeof(decimal))] + public void InvalidProperty(Type type) + { + if(type == typeof(bool)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + #region String + + if(type == typeof(Guid)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(DateTime)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(DateTimeOffset)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(TimeSpan)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(char)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(Foo)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(string)) + { + TestInvalidNullableProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": {Fixture.Create()} }}") + .GetProperty("input")); + return; + } + + #endregion + + #region Integer + + if(type == typeof(byte)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": {Fixture.Create().ToString().ToLower()} }}") + .GetProperty("input")); + return; + } + + if(type == typeof(sbyte)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(short)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(ushort)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(int)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(uint)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(long)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(ulong)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + #endregion + + #region Number + + if(type == typeof(float)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(double)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + if(type == typeof(decimal)) + { + TestInvalidValidProperty(() => + JsonSerializer.Deserialize($@"{{ ""input"": ""{Fixture.Create()}"" }}") + .GetProperty("input")); + return; + } + + #endregion + } + + private void TestInvalidValidProperty(Func createJsonElement) + where T : struct + { + var thing = new StructPropertyThing(); + var context = Factory.Create(thing, new ThingOption()); + + thing.ThingContext = context; + + context.Actions.Should().BeEmpty(); + context.Events.Should().BeEmpty(); + + context.Properties.Should().NotBeEmpty(); + context.Properties.Should().HaveCount(2); + context.Properties.Should().ContainKey(nameof(StructPropertyThing.Value)); + context.Properties.Should().ContainKey(nameof(StructPropertyThing.NullableValue)); + + var value = Fixture.Create(); + var jsonElement = createJsonElement(); + + var defaultValue = Fixture.Create(); + thing.Value = defaultValue; + context.Properties[nameof(StructPropertyThing.Value)].SetValue(jsonElement).Should().Be(SetPropertyResult.InvalidValue); + thing.Value.Should().NotBe(value); + thing.Value.Should().Be(defaultValue); + context.Properties[nameof(StructPropertyThing.Value)].GetValue().Should().Be(defaultValue); + + thing.NullableValue = defaultValue; + context.Properties[nameof(StructPropertyThing.NullableValue)].SetValue(jsonElement).Should().Be(SetPropertyResult.InvalidValue); + thing.NullableValue.Should().NotBe(value); + thing.NullableValue.Should().Be(defaultValue); + context.Properties[nameof(StructPropertyThing.NullableValue)].GetValue().Should().Be(defaultValue); + } + + private void TestInvalidNullableProperty(Func createJsonElement) + { + var thing = new NullablePropertyThing(); + var context = Factory.Create(thing, new ThingOption()); + + thing.ThingContext = context; + + context.Actions.Should().BeEmpty(); + context.Events.Should().BeEmpty(); + + context.Properties.Should().NotBeEmpty(); + context.Properties.Should().HaveCount(1); + context.Properties.Should().ContainKey(nameof(NullablePropertyThing.Value)); + + var value = Fixture.Create(); + var jsonElement = createJsonElement(); + + var defaultValue = Fixture.Create(); + thing.Value = defaultValue; + context.Properties[nameof(NullablePropertyThing.Value)].SetValue(jsonElement).Should().Be(SetPropertyResult.InvalidValue); + thing.Value.Should().NotBe(value); + thing.Value.Should().Be(defaultValue); + context.Properties[nameof(NullablePropertyThing.Value)].GetValue().Should().Be(defaultValue); + } + #endregion + + public class StructPropertyThing : Thing + where T : struct + { + public override string Name => "property-thing"; + + public T Value { get; set; } + public T? NullableValue { get; set; } + } + + public class NullablePropertyThing : Thing + { + public override string Name => "property-thing"; + public T Value { get; set; } + } + + public enum Foo + { + A, + Bar, + C + } + + private const string RESPONSE_WITH_NULLABLE = @"{{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""properties"": {{ + ""value"": {{ + ""type"": ""{0}"", + ""isReadOnly"": false, + {1} + ""link"": [ + {{ + ""href"": ""/things/property-thing/properties/value"", + ""rel"": ""property"" + }} + ] + }}, + ""nullableValue"": {{ + ""type"": ""{0}"", + ""isReadOnly"": false, + {1} + ""link"": [ + {{ + ""href"": ""/things/property-thing/properties/nullableValue"", + ""rel"": ""property"" + }} + ] + }} + }}, + ""links"": [ + {{ + ""href"": ""properties"", + ""rel"": ""/things/property-thing/properties"" + }}, + {{ + ""href"": ""events"", + ""rel"": ""/things/property-thing/events"" + }}, + {{ + ""href"": ""actions"", + ""rel"": ""/things/property-thing/actions"" + }} + ] +}}"; + + private const string RESPONSE_WITHOUT_NULLABLE = @"{{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""properties"": {{ + ""value"": {{ + ""type"": ""{0}"", + ""isReadOnly"": false, + {1} + ""link"": [ + {{ + ""href"": ""/things/property-thing/properties/value"", + ""rel"": ""property"" + }} + ] + }} + }}, + ""links"": [ + {{ + ""href"": ""properties"", + ""rel"": ""/things/property-thing/properties"" + }}, + {{ + ""href"": ""events"", + ""rel"": ""/things/property-thing/events"" + }}, + {{ + ""href"": ""actions"", + ""rel"": ""/things/property-thing/actions"" + }} + ] +}}"; + } +} diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Mozilla.IoT.WebThing.Intregration.Test.csproj b/test/Mozilla.IoT.WebThing.Intregration.Test/Mozilla.IoT.WebThing.Intregration.Test.csproj new file mode 100644 index 0000000..a948913 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Mozilla.IoT.WebThing.Intregration.Test.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + + diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Http/Properties.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Http/Properties.cs new file mode 100644 index 0000000..124264d --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Http/Properties.cs @@ -0,0 +1,67 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using AutoFixture; +using FluentAssertions; +using Microsoft.AspNetCore.TestHost; +using Mozilla.IoT.WebThing.Intregration.Test.Web.Things; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Web.Http +{ + public class Properties + { + private static readonly TimeSpan s_timeout = TimeSpan.FromSeconds(30); + private readonly Fixture _fixture; + private readonly HttpClient _client; + + public Properties() + { + _fixture = new Fixture(); + + var host = Program.GetHost().GetAwaiter().GetResult(); + _client = host.GetTestServer().CreateClient(); + } + + [Fact] + public async Task ThingNotFound() + { + var source = new CancellationTokenSource(); + source.CancelAfter(s_timeout); + var response = await _client.GetAsync($"/things/{_fixture.Create()}/properties", + source.Token).ConfigureAwait(false); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task GetProperties() + { + var source = new CancellationTokenSource(); + source.CancelAfter(s_timeout); + var response = await _client.GetAsync($"/things/{ImmutablePropertyThing.NAME}/properties", + source.Token).ConfigureAwait(false); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + response.Content.Headers.ContentType.ToString().Should().Be("application/json"); + + var message = await response.Content.ReadAsStringAsync() + .ConfigureAwait(false); + var json = JToken.Parse(message); + json.Type.Should().Be(JTokenType.Object); + json.Type.Should().Be(JTokenType.Object); + FluentAssertions.Json.JsonAssertionExtensions + .Should(json) + .BeEquivalentTo(JToken.Parse(@" +{ + ""isEnable"": false, + ""level"": 10, + ""value"": ""test"" +} +")); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.AcceptanceTest/Program.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Program.cs similarity index 81% rename from test/Mozilla.IoT.WebThing.AcceptanceTest/Program.cs rename to test/Mozilla.IoT.WebThing.Intregration.Test/Web/Program.cs index 22cf06a..03df7f7 100644 --- a/test/Mozilla.IoT.WebThing.AcceptanceTest/Program.cs +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Program.cs @@ -1,26 +1,23 @@ -using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Extensions; -namespace Mozilla.IoT.WebThing.AcceptanceTest +namespace Mozilla.IoT.WebThing.Intregration.Test.Web { public class Program { - public static IHostBuilder CreateHostBuilder(string[] args, Action? option = null) => + public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureLogging(logger => { logger.ClearProviders() .AddConsole() - .AddFilter("*", LogLevel.Debug); + .AddFilter("Microsoft.*", LogLevel.Critical); }) .ConfigureWebHostDefaults(webBuilder => { - Startup.Option = option; webBuilder .UseTestServer() .UseStartup(); @@ -40,7 +37,7 @@ public static ValueTask GetHost() private static async Task CreateHostBuilderAndStartAsync(string[] args) { - return s_defaultHost = await Host.CreateDefaultBuilder(args) + return s_defaultHost = await Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Startup.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Startup.cs new file mode 100644 index 0000000..06d65de --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Startup.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.WebSockets; +using Microsoft.Extensions.DependencyInjection; +using Mozilla.IoT.WebThing.Intregration.Test.Web.Things; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Web +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddThings() + .AddThing() + .AddThing(); + + services.AddWebSockets(_ => { }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + app.UseRouting(); + + app.UseWebSockets(); + + app.UseEndpoints(endpoints => + { + endpoints.MapThings(); + }); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Things/PropertyThing.cs b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Things/PropertyThing.cs new file mode 100644 index 0000000..cbd803f --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Intregration.Test/Web/Things/PropertyThing.cs @@ -0,0 +1,36 @@ +using System; +using Mozilla.IoT.WebThing.Attributes; + +namespace Mozilla.IoT.WebThing.Intregration.Test.Web.Things +{ + public class ImmutablePropertyThing : Thing + { + public const string NAME = "immutable-property-thing"; + public override string Name => NAME; + + public bool IsEnable { get; set; } + public int Level { get; set; } = 10; + public string Value { get; set; } = "test"; + } + + public class PropertyThing : Thing + { + public override string Name => "property-thing"; + + [ThingProperty(Ignore = true)] + public string NoShow { get; set; } + + [ThingProperty(IsReadOnly = true)] + public Guid Id { get; set; } = Guid.NewGuid(); + + [ThingProperty(Name = "isEnable")] + public bool Enable { get; set; } + + [ThingProperty(Minimum = 0, Maximum = 100)] + public int Level { get; set; } + + [ThingProperty(MinimumLength = 1, MaximumLength = 100, + Pattern = @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$")] + public string Email { get; set; } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/ActionCollectionTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/ActionCollectionTest.cs new file mode 100644 index 0000000..a9d2b3e --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/ActionCollectionTest.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using AutoFixture; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing.Actions; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions +{ + public class ActionCollectionTest + { + private readonly Fixture _fixture; + private readonly IActionInfoFactory _factory; + private readonly JsonElement _input; + private readonly string _parameterName; + private readonly int _parameterValue; + private readonly IActionParameter _parameter; + private readonly Dictionary _parameters; + private readonly DictionaryInputConvert _convert; + private readonly ActionCollection _collection; + + public ActionCollectionTest() + { + _fixture = new Fixture(); + _parameterName = _fixture.Create(); + _parameterValue = _fixture.Create(); + + _factory = Substitute.For(); + _parameter = Substitute.For(); + _parameters = new Dictionary + { + [_parameterName] = _parameter + }; + + _input = JsonSerializer.Deserialize($@"{{ ""input"": {{ + ""{_parameterName}"": {_parameterValue} + }} + }}"); + + _convert = new DictionaryInputConvert(_parameters); + _collection = new ActionCollection(_convert, _factory); + } + + #region TryAdd + + [Fact] + public void TryAddWithSuccess() + { + _parameter.TryGetValue(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = _parameterValue; + return true; + }); + + var actionInfo = Substitute.For(); + + _factory.CreateActionInfo(Arg.Any>()) + .Returns(actionInfo); + + _collection.TryAdd(_input, out var action).Should().BeTrue(); + action.Should().NotBeNull(); + + _parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .Received(1) + .CreateActionInfo(Arg.Any>()); + } + + [Fact] + public void TryAddWhenInputNotExist() + { + var input = JsonSerializer.Deserialize($@"{{ ""{_parameterName}"": {_parameterValue} }}"); + + _collection.TryAdd(input, out var action).Should().BeFalse(); + + action.Should().BeNull(); + + _parameter + .DidNotReceive() + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .Received(1) + .CreateActionInfo(Arg.Any>()); + } + + [Fact] + public void TryAddWhenCouldNotConvert() + { + _parameter.TryGetValue(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = null; + return false; + }); + + _collection.TryAdd(_input, out var action).Should().BeFalse(); + action.Should().BeNull(); + + _parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .DidNotReceive() + .CreateActionInfo(Arg.Any>()); + } + + #endregion + + #region TryGetValue + + [Fact] + public void TryGetWithSuccess() + { + _parameter.TryGetValue(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = _parameterValue; + return true; + }); + + var actionInfo = Substitute.For(); + + _factory.CreateActionInfo(Arg.Any>()) + .Returns(actionInfo); + + _collection.TryAdd(_input, out var action).Should().BeTrue(); + action.Should().NotBeNull(); + + _collection.TryGetValue(action.GetId(), out var getAction).Should().BeTrue(); + + getAction.Should().NotBeNull(); + getAction.Should().Be(action); + + _parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .Received(1) + .CreateActionInfo(Arg.Any>()); + } + + [Fact] + public void TryGetWhenActionDoesNotExist() + { + _collection.TryGetValue(_fixture.Create(), out var action).Should().BeFalse(); + action.Should().BeNull(); + } + + #endregion + + #region TryRemove + + [Fact] + public void TryRemoveWithSuccess() + { + _parameter.TryGetValue(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = _parameterValue; + return true; + }); + + var actionInfo = Substitute.For(); + + _factory.CreateActionInfo(Arg.Any>()) + .Returns(actionInfo); + + _collection.TryAdd(_input, out var action).Should().BeTrue(); + action.Should().NotBeNull(); + + _collection.TryRemove(action.GetId(), out var getAction).Should().BeTrue(); + + getAction.Should().NotBeNull(); + getAction.Should().Be(action); + + _parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .Received(1) + .CreateActionInfo(Arg.Any>()); + } + + [Fact] + public void TryRemoveWhenActionDoesNotExist() + { + _collection.TryRemove(_fixture.Create(), out var action).Should().BeFalse(); + action.Should().BeNull(); + } + + #endregion + + #region OnStatusChange + + [Fact] + public void OnStatusChange() + { + var counter = 0; + _collection.Change += OnActionStatusChange; + _parameter.TryGetValue(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = _parameterValue; + return true; + }); + + var actionInfo = new VoidActionInfo(); + + _factory.CreateActionInfo(Arg.Any>()) + .Returns(actionInfo); + + _collection.TryAdd(_input, out var action).Should().BeTrue(); + action.Should().NotBeNull(); + + var provider = Substitute.For(); + provider.GetService(typeof(ILogger)) + .Returns(Substitute.For>()); + + actionInfo.ExecuteAsync(Substitute.For(), provider); + + counter.Should().Be(3); + + counter = 0; + + _collection.TryRemove(actionInfo.GetId(), out _); + + actionInfo.ExecuteAsync(Substitute.For(), provider); + counter.Should().Be(0); + + _parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _factory + .Received(1) + .CreateActionInfo(Arg.Any>()); + + + + void OnActionStatusChange(object sender, ActionInfo info) + { + counter++; + } + } + + #endregion + + public class VoidActionInfo : ActionInfo + { + public List Logs { get; } = new List(); + protected override ValueTask InternalExecuteAsync(Thing thing, IServiceProvider provider) + { + Logs.Add(nameof(VoidActionInfo)); + return new ValueTask(); + } + + public override string GetActionName() + => "void-action"; + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/ActionInfoTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/ActionInfoTest.cs new file mode 100644 index 0000000..5b255b2 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/ActionInfoTest.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing.Actions; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions +{ + public class ActionInfoTest + { + private readonly IServiceProvider _provider; + private readonly ILogger _logger; + + public ActionInfoTest() + { + _provider = Substitute.For(); + _logger = Substitute.For>(); + + _provider.GetService(typeof(ILogger)) + .Returns(_logger); + } + + [Fact] + public void Execute() + { + var action = new VoidActionInfo(); + + action.GetId().Should().NotBeEmpty(); + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Pending); + action.GetActionName().Should().Be("void-action"); + + action.ExecuteAsync(Substitute.For(), _provider); + + action.TimeCompleted.Should().NotBeNull(); + action.Status.Should().Be(ActionStatus.Completed); + + action.Logs.Should().NotBeEmpty(); + action.Logs.Should().HaveCount(1); + action.Logs.Should().BeEquivalentTo(new List + { + nameof(VoidActionInfo) + }); + } + + [Fact] + public void ExecuteWithThrow() + { + var action = new VoidActionInfo(); + + action.GetId().Should().NotBeEmpty(); + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Pending); + action.GetActionName().Should().Be("void-action"); + + action.ExecuteAsync(Substitute.For(), _provider); + + action.TimeCompleted.Should().NotBeNull(); + action.Status.Should().Be(ActionStatus.Completed); + + action.Logs.Should().NotBeEmpty(); + action.Logs.Should().HaveCount(1); + action.Logs.Should().BeEquivalentTo(new List + { + nameof(VoidActionInfo) + }); + } + + [Fact] + public async Task ExecuteAsync() + { + var action = new LongRunningActionInfo(); + + action.GetId().Should().NotBeEmpty(); + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Pending); + action.GetActionName().Should().Be("long-running-action"); + + var task = action.ExecuteAsync(Substitute.For(), _provider); + + action.Logs.Should().BeEmpty(); + + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Executing); + + await task; + + action.TimeCompleted.Should().NotBeNull(); + action.Status.Should().Be(ActionStatus.Completed); + + action.Logs.Should().NotBeEmpty(); + action.Logs.Should().HaveCount(1); + action.Logs.Should().BeEquivalentTo(new List + { + nameof(LongRunningActionInfo) + }); + } + + [Fact] + public async Task Cancel() + { + var action = new LongRunningActionInfo(); + + action.GetId().Should().NotBeEmpty(); + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Pending); + action.GetActionName().Should().Be("long-running-action"); + + var task = action.ExecuteAsync(Substitute.For(), _provider); + + action.Logs.Should().BeEmpty(); + + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Executing); + + action.Cancel(); + + await Task.Delay(200); + action.TimeCompleted.Should().NotBeNull(); + action.Status.Should().Be(ActionStatus.Completed); + + action.Logs.Should().BeEmpty(); + } + + [Fact] + public async Task StatusChange() + { + var counter = 0; + var action = new VoidActionInfo(); + + action.GetId().Should().NotBeEmpty(); + action.TimeCompleted.Should().BeNull(); + action.Status.Should().Be(ActionStatus.Pending); + action.GetActionName().Should().Be("void-action"); + + action.StatusChanged += OnStatusChange; + + await action.ExecuteAsync(Substitute.For(), _provider); + + action.TimeCompleted.Should().NotBeNull(); + action.Status.Should().Be(ActionStatus.Completed); + + action.Logs.Should().NotBeEmpty(); + action.Logs.Should().HaveCount(1); + action.Logs.Should().BeEquivalentTo(new List + { + nameof(VoidActionInfo) + }); + + void OnStatusChange(object sender, EventArgs args) + { + ((ActionInfo)sender).Status.Should().Be((ActionStatus)counter++); + } + } + + public class VoidActionInfo : ActionInfo + { + public List Logs { get; } = new List(); + protected override ValueTask InternalExecuteAsync(Thing thing, IServiceProvider provider) + { + Logs.Add(nameof(VoidActionInfo)); + return new ValueTask(); + } + + public override string GetActionName() + => "void-action"; + } + + public class LongRunningActionInfo : ActionInfo + { + public List Logs { get; } = new List(); + protected override async ValueTask InternalExecuteAsync(Thing thing, IServiceProvider provider) + { + await Task.Delay(3_000, Source.Token); + Logs.Add(nameof(LongRunningActionInfo)); + } + + public override string GetActionName() + => "long-running-action"; + } + + public class ExceptionActionInfo : ActionInfo + { + protected override ValueTask InternalExecuteAsync(Thing thing, IServiceProvider provider) + { + throw new NotImplementedException(); + } + + public override string GetActionName() + => "Exception-Action"; + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/InfoConvertTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/InfoConvertTest.cs new file mode 100644 index 0000000..5fe5815 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/InfoConvertTest.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions +{ + public class ActionInfoConvertTest + { + private readonly Fixture _fixture; + private readonly Dictionary _parameters; + private readonly DictionaryInputConvert _convert; + + public ActionInfoConvertTest() + { + _fixture = new Fixture(); + _parameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + _convert = new DictionaryInputConvert(_parameters); + } + + [Fact] + public void TryConvertWithSuccess() + { + var parameter = Substitute.For(); + + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + _parameters.Add(parameterName, parameter); + + parameter.TryGetValue(Arg.Any(), out Arg.Any() ) + .Returns(x => + { + x[1] = parameterValue; + return true; + }); + + var json = JsonSerializer.Deserialize($@"{{ ""{parameterName}"": {parameterValue} }}"); + _convert.TryConvert(json, out var result).Should().BeTrue(); + + result.Should().NotBeEmpty(); + result.Should().HaveCount(1); + result.Should().BeEquivalentTo(new Dictionary + { + [parameterName] = parameterValue + }); + + parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + } + + [Fact] + public void TryConvertWithSuccessWhenActionCanBeNull() + { + var parameter = Substitute.For(); + + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + _parameters.Add(parameterName, parameter); + + parameter.TryGetValue(Arg.Any(), out Arg.Any() ) + .Returns(x => + { + x[1] = parameterValue; + return true; + }); + + var parameterNullName = _fixture.Create(); + var parameterNull = Substitute.For(); + parameterNull.CanBeNull.Returns(true); + _parameters.Add(parameterNullName, parameterNull); + + var json = JsonSerializer.Deserialize($@"{{ + ""{parameterName}"": {parameterValue} + }}"); + _convert.TryConvert(json, out var result).Should().BeTrue(); + + result.Should().NotBeEmpty(); + result.Should().HaveCount(2); + result.Should().BeEquivalentTo(new Dictionary + { + [parameterName] = parameterValue, + [parameterNullName] = null + }); + + parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _ = parameterNull + .Received(1) + .CanBeNull; + } + + [Fact] + public void TryConvertWhenParameterDoesNotExist() + { + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + + var json = JsonSerializer.Deserialize($@"{{ ""{parameterName}"": {parameterValue} }}"); + _convert.TryConvert(json, out var result).Should().BeFalse(); + + result.Should().BeNull(); + } + + [Fact] + public void TryConvertWhenCouldNotGet() + { + var parameter = Substitute.For(); + + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + _parameters.Add(parameterName, parameter); + + parameter.TryGetValue(Arg.Any(), out Arg.Any() ) + .Returns(x => + { + x[1] = null; + return false; + }); + + var json = JsonSerializer.Deserialize($@"{{ ""{parameterName}"": {parameterValue} }}"); + _convert.TryConvert(json, out var result).Should().BeFalse(); + + result.Should().BeNull(); + + parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + } + + [Fact] + public void TryConvertWhenInputAlreadyExist() + { + var parameter = Substitute.For(); + + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + _parameters.Add(parameterName, parameter); + + parameter.TryGetValue(Arg.Any(), out Arg.Any() ) + .Returns(x => + { + x[1] = parameterValue; + return true; + }); + + var json = JsonSerializer.Deserialize($@"{{ + ""{parameterName}"": {parameterValue}, + ""{parameterName}"": {parameterValue} + }}"); + + _convert.TryConvert(json, out var result).Should().BeFalse(); + + result.Should().BeNull(); + + parameter + .Received(2) + .TryGetValue(Arg.Any(), out Arg.Any()); + } + + [Fact] + public void TryConvertWhenActionCannotBeNull() + { + var parameter = Substitute.For(); + + var parameterName = _fixture.Create(); + var parameterValue = _fixture.Create(); + _parameters.Add(parameterName, parameter); + + parameter.TryGetValue(Arg.Any(), out Arg.Any() ) + .Returns(x => + { + x[1] = parameterValue; + return true; + }); + + var parameterNullName = _fixture.Create(); + var parameterNull = Substitute.For(); + + parameterNull.CanBeNull.Returns(false); + _parameters.Add(parameterNullName, parameterNull); + + var json = JsonSerializer.Deserialize($@"{{ + ""{parameterName}"": {parameterValue} + }}"); + _convert.TryConvert(json, out var result).Should().BeFalse(); + + result.Should().BeNull(); + + parameter + .Received(1) + .TryGetValue(Arg.Any(), out Arg.Any()); + + _ = parameterNull + .Received(1) + .CanBeNull; + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Boolean/ParameterBooleanTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Boolean/ParameterBooleanTest.cs new file mode 100644 index 0000000..6297072 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Boolean/ParameterBooleanTest.cs @@ -0,0 +1,94 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Boolean; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Boolean +{ + public class ParameterBooleanTest + { + private readonly Fixture _fixture; + + public ParameterBooleanTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + + private static ParameterBoolean CreateNoNullable() + => new ParameterBoolean(false); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetNoNullableWithValue(bool value) + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value.ToString().ToLower()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + + #region Nullable + + private static ParameterBoolean CreateNullable() + => new ParameterBoolean(true); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetNullableWithValue(bool value) + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value.ToString().ToLower()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create().ToString()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterByteTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterByteTest.cs new file mode 100644 index 0000000..160ee69 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterByteTest.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterByteTest + { + private readonly Fixture _fixture; + + public ParameterByteTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterByte CreateNoNullable(byte[]? enums = null, byte? min = null, byte? max = null, byte? multipleOf = null) + => new ParameterByte(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(byte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(byte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(byte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(byte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private ParameterByte CreateNullable(byte[]? enums = null, byte? min = null, byte? max = null, byte? multipleOf = null) + => new ParameterByte(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(byte value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(byte value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToByte(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToByte(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDecimalTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDecimalTest.cs new file mode 100644 index 0000000..6209f14 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDecimalTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterDecimalTest + { + private readonly Fixture _fixture; + + public ParameterDecimalTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterDecimal CreateNoNullable(decimal[]? enums = null, decimal? min = null, decimal? max = null, decimal? multipleOf = null) + => new ParameterDecimal(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(decimal value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(decimal value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(decimal value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(decimal value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterDecimal CreateNullable(decimal[]? enums = null, decimal? min = null, decimal? max = null, decimal? multipleOf = null) + => new ParameterDecimal(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(decimal value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(decimal value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(decimal value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(decimal value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDoubleTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDoubleTest.cs new file mode 100644 index 0000000..e02d1f4 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterDoubleTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterDoubleTest + { + private readonly Fixture _fixture; + + public ParameterDoubleTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterDouble CreateNoNullable(double[]? enums = null, double? min = null, double? max = null, double? multipleOf = null) + => new ParameterDouble(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(double value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(double value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(double value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(double value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterDouble CreateNullable(double[]? enums = null, double? min = null, double? max = null, double? multipleOf = null) + => new ParameterDouble(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(double value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(double value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(double value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(double value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterFloatTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterFloatTest.cs new file mode 100644 index 0000000..335a860 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterFloatTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterFloatTest + { + private readonly Fixture _fixture; + + public ParameterFloatTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterFloat CreateNoNullable(float[]? enums = null, float? min = null, float? max = null, float? multipleOf = null) + => new ParameterFloat(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(float value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(float value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(float value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(float value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterFloat CreateNullable(float[]? enums = null, float? min = null, float? max = null, float? multipleOf = null) + => new ParameterFloat(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(float value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(float value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(float value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(float value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterIntTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterIntTest.cs new file mode 100644 index 0000000..34f4835 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterIntTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterIntTest + { + private readonly Fixture _fixture; + + public ParameterIntTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterInt CreateNoNullable(int[]? enums = null, int? min = null, int? max = null, int? multipleOf = null) + => new ParameterInt(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(int value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(int value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(int value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(int value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterInt CreateNullable(int[]? enums = null, int? min = null, int? max = null, int? multipleOf = null) + => new ParameterInt(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(int value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(int value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterLongTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterLongTest.cs new file mode 100644 index 0000000..740398a --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterLongTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterLongTest + { + private readonly Fixture _fixture; + + public ParameterLongTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterLong CreateNoNullable(long[]? enums = null, long? min = null, long? max = null, long? multipleOf = null) + => new ParameterLong(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(long value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(long value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(long value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(long value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterLong CreateNullable(long[]? enums = null, long? min = null, long? max = null, long? multipleOf = null) + => new ParameterLong(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(long value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(long value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(long value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(long value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterSByteTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterSByteTest.cs new file mode 100644 index 0000000..fd4017c --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterSByteTest.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterSByteTest + { + private readonly Fixture _fixture; + + public ParameterSByteTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterSByte CreateNoNullable(sbyte[]? enums = null, sbyte? min = null, sbyte? max = null, sbyte? multipleOf = null) + => new ParameterSByte(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(sbyte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(sbyte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(sbyte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(sbyte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterSByte CreateNullable(sbyte[]? enums = null, sbyte? min = null, sbyte? max = null, sbyte? multipleOf = null) + => new ParameterSByte(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(sbyte value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(sbyte value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToSByte(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToSByte(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterShortTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterShortTest.cs new file mode 100644 index 0000000..97925b8 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterShortTest.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterShortTest + { + private readonly Fixture _fixture; + + public ParameterShortTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterShort CreateNoNullable(short[]? enums = null, short? min = null, short? max = null, short? multipleOf = null) + => new ParameterShort(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(short value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(short value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(short value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(short value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterShort CreateNullable(short[]? enums = null, short? min = null, short? max = null, short? multipleOf = null) + => new ParameterShort(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(short value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(short value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToInt16(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToInt16(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUIntTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUIntTest.cs new file mode 100644 index 0000000..aded43d --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUIntTest.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterUIntTest + { + private readonly Fixture _fixture; + + public ParameterUIntTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterUInt CreateNoNullable(uint[]? enums = null, uint? min = null, uint? max = null, uint? multipleOf = null) + => new ParameterUInt(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(uint value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(uint value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(uint value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(uint value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterUInt CreateNullable(uint[]? enums = null, uint? min = null, uint? max = null, uint? multipleOf = null) + => new ParameterUInt(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(uint value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(uint value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToUInt32(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToUInt32(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterULongTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterULongTest.cs new file mode 100644 index 0000000..857a2b8 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterULongTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterULongTest + { + private readonly Fixture _fixture; + + public ParameterULongTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterULong CreateNoNullable(ulong[]? enums = null, ulong? min = null, ulong? max = null, ulong? multipleOf = null) + => new ParameterULong(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(ulong value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(ulong value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(ulong value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(ulong value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterULong CreateNullable(ulong[]? enums = null, ulong? min = null, ulong? max = null, ulong? multipleOf = null) + => new ParameterULong(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(ulong value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(ulong value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(ulong value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(ulong value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUShortTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUShortTest.cs new file mode 100644 index 0000000..2233dd3 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/Numbers/ParameterUShortTest.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.Numbers +{ + public class ParameterUShortTest + { + private readonly Fixture _fixture; + + public ParameterUShortTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterUShort CreateNoNullable(ushort[]? enums = null, ushort? min = null, ushort? max = null, ushort? multipleOf = null) + => new ParameterUShort(false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(ushort value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(ushort value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(ushort value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(ushort value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterUShort CreateNullable(ushort[]? enums = null, ushort? min = null, ushort? max = null, ushort? multipleOf = null) + => new ParameterUShort(true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(ushort value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(ushort value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToUInt16(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToUInt16(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterCharTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterCharTest.cs new file mode 100644 index 0000000..60876c8 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterCharTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterCharTest + { + private readonly Fixture _fixture; + + public ParameterCharTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterChar CreateNoNullable(char[] enums = null) + => new ParameterChar(false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterChar CreateNullable(char[] enums = null) + => new ParameterChar(true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeOffsetTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeOffsetTest.cs new file mode 100644 index 0000000..147cb85 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeOffsetTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterDateTimeOffsetTest + { + private readonly Fixture _fixture; + + public ParameterDateTimeOffsetTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterDateTimeOffset CreateNoNullable(DateTimeOffset[]? enums = null) + => new ParameterDateTimeOffset(false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterDateTimeOffset CreateNullable(DateTimeOffset[] enums = null) + => new ParameterDateTimeOffset(true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeTest.cs new file mode 100644 index 0000000..6645940 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterDateTimeTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterDateTimeTest + { + private readonly Fixture _fixture; + + public ParameterDateTimeTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterDateTime CreateNoNullable(DateTime[]? enums = null) + => new ParameterDateTime(false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterDateTime CreateNullable(DateTime[] enums = null) + => new ParameterDateTime(true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterEnumTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterEnumTest.cs new file mode 100644 index 0000000..58eb14b --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterEnumTest.cs @@ -0,0 +1,138 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterEnumTest + { + private readonly Fixture _fixture; + + public ParameterEnumTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterEnum CreateNoNullable() + => new ParameterEnum(false, typeof(Foo)); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var property = CreateNoNullable(); + foreach (var value in typeof(Foo).GetEnumValues()) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterEnum CreateNullable() + => new ParameterEnum(true, typeof(Foo)); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var property = CreateNullable(); + foreach (var value in typeof(Foo).GetEnumValues()) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + + public enum Foo + { + A, + B, + C, + D + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterGuidTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterGuidTest.cs new file mode 100644 index 0000000..0dc6ee5 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterGuidTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterGuidTest + { + private readonly Fixture _fixture; + + public ParameterGuidTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterGuid CreateNoNullable(Guid[] enums = null) + => new ParameterGuid(false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterGuid CreateNullable(Guid[] enums = null) + => new ParameterGuid(true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterStringTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterStringTest.cs new file mode 100644 index 0000000..8dea26a --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterStringTest.cs @@ -0,0 +1,165 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterStringTest + { + private readonly Fixture _fixture; + + public ParameterStringTest() + { + _fixture = new Fixture(); + } + + private static ParameterString CreateProperty(string[]? enums = null, string pattern = null, int? minimum = null, int? maximum = null, bool isNullable = false) + => new ParameterString(isNullable, minimum, maximum, pattern, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateProperty(isNullable: true); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateProperty(isNullable: true); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateProperty(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void SetNoNullableWithValuePattern() + { + var property = CreateProperty(pattern: @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$"); + const string value = "test@test.com"; + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var json).Should().BeTrue(); + json.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMinLength() + { + var property = CreateProperty(minimum: 1); + var value = _fixture.Create().ToString(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + + value = _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMaxLength() + { + var property = CreateProperty(maximum: 37); + var value = _fixture.Create(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + + value = _fixture.Create() + _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(bool))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"{_fixture.Create().ToString().ToLower()}"; + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateProperty(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMinLength() + { + var property = CreateProperty(minimum: 1); + var value = string.Empty; + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + + value = _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": null }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithMaxLength() + { + var property = CreateProperty(maximum: 36); + var value = _fixture.Create() + _fixture.Create(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": null }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithValuePattern() + { + var property = CreateProperty(pattern: @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$"); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterTimeSpanTest.cs b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterTimeSpanTest.cs new file mode 100644 index 0000000..16cda04 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Actions/Parameters/String/ParameterTimeSpanTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions.Parameters.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Actions.Parameters.String +{ + public class ParameterTimeSpanTest + { + private readonly Fixture _fixture; + + public ParameterTimeSpanTest() + { + _fixture = new Fixture(); + } + + #region No Nullable + private static ParameterTimeSpan CreateNoNullable(TimeSpan[]? enums = null) + => new ParameterTimeSpan(false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + #endregion + + #region Nullable + + private static ParameterTimeSpan CreateNullable(TimeSpan[] enums = null) + => new ParameterTimeSpan(true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out var jsonValue).Should().BeTrue(); + jsonValue.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.TryGetValue(jsonElement.GetProperty("input"), out _).Should().BeFalse(); + } + + #endregion + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Builder/ActionBuilderTest.cs b/test/Mozilla.IoT.WebThing.Test/Builder/ActionBuilderTest.cs new file mode 100644 index 0000000..c30a719 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Builder/ActionBuilderTest.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Builder +{ + public class ActionBuilderTest + { + private readonly Fixture _fixture; + private readonly IActionParameterFactory _factory; + private readonly ActionBuilder _builder; + private readonly ActionThing _thing; + + public ActionBuilderTest() + { + _fixture = new Fixture(); + _thing = new ActionThing(); + _factory = Substitute.For(); + _builder = new ActionBuilder(_factory); + } + + + [Fact] + public void TryAddWhenSetThingTypeIsNotCalled() + => Assert.Throws(() => _builder.Add(Substitute.For(), + Substitute.For())); + + [Fact] + public void TryBuildWhenIsNotSetSetThing() + => Assert.Throws(() => _builder.Build()); + + [Fact] + public void TryBuildWhenIsNotSetThingType() + { + _builder.SetThing(_thing); + Assert.Throws(() => _builder.Build()); + } + + [Fact] + public void BuildWithActionWithNoParameter() + { + _builder.SetThing(_thing) + .SetThingType(_thing.GetType()) + .SetThingOption(new ThingOption()); + + var method = typeof(ActionThing).GetMethod(nameof(ActionThing.NoParameter)); + + _builder.Add(method!, null); + + var actions = _builder.Build(); + actions.Should().NotBeNull(); + actions.Should().NotBeEmpty(); + actions.Should().HaveCount(1); + actions.Should().ContainKey(nameof(ActionThing.NoParameter)); + + _factory.DidNotReceive().Create(Arg.Any(), Arg.Any()); + } + + [Fact] + public void BuildWithActionWithParameter() + { + _builder.SetThing(_thing) + .SetThingType(_thing.GetType()) + .SetThingOption(new ThingOption()); + + var method = typeof(ActionThing).GetMethod(nameof(ActionThing.WithParameter)); + + _builder.Add(method!, null); + + var parameters = new List<(ParameterInfo, Information)>(); + + foreach (var parameter in method.GetParameters()) + { + var info = new Information(null, null, null, null, null, + null, null, null, null, false, + parameter.Name!, _fixture.Create()); + + parameters.Add((parameter, info)); + _factory + .Create(parameter.ParameterType, info) + .Returns(Substitute.For()); + + _builder.Add(parameter, info); + } + + var actions = _builder.Build(); + actions.Should().NotBeNull(); + actions.Should().NotBeEmpty(); + actions.Should().HaveCount(1); + actions.Should().ContainKey(nameof(ActionThing.WithParameter)); + + foreach (var (parameter, info) in parameters) + { + _factory.Received(1) + .Create(parameter.ParameterType, info ); + } + } + + [Fact] + public void BuildWithMultiActions() + { + _builder.SetThing(_thing) + .SetThingType(_thing.GetType()) + .SetThingOption(new ThingOption()); + + var withParameter = typeof(ActionThing).GetMethod(nameof(ActionThing.WithParameter)); + + _builder.Add(withParameter!, null); + + var parameters = new List<(ParameterInfo, Information)>(); + + foreach (var parameter in withParameter.GetParameters()) + { + var info = new Information(null, null, null, null, null, + null, null, null, null, false, + parameter.Name!, _fixture.Create()); + + parameters.Add((parameter, info)); + _factory + .Create(parameter.ParameterType, info) + .Returns(Substitute.For()); + + _builder.Add(parameter, info); + } + + var noParameter = typeof(ActionThing).GetMethod(nameof(ActionThing.NoParameter)); + _builder.Add(noParameter!, null); + + + var actions = _builder.Build(); + actions.Should().NotBeNull(); + actions.Should().NotBeEmpty(); + actions.Should().HaveCount(2); + actions.Should().ContainKey(nameof(ActionThing.NoParameter)); + actions.Should().ContainKey(nameof(ActionThing.WithParameter)); + + foreach (var (parameter, info) in parameters) + { + _factory.Received(1) + .Create(parameter.ParameterType, info ); + } + } + + public class ActionThing : Thing + { + public override string Name => "action-thing"; + + + public void NoParameter() + { + + } + + public void WithParameter(string value, int id) + { + } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Builder/EventBuilderTest.cs b/test/Mozilla.IoT.WebThing.Test/Builder/EventBuilderTest.cs new file mode 100644 index 0000000..067019a --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Builder/EventBuilderTest.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Properties; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Builder +{ + public class EventBuilderTest + { + private readonly EventBuilder _builder; + private readonly EventThing _thing; + private readonly Fixture _fixture; + + public EventBuilderTest() + { + _builder = new EventBuilder(); + _thing = new EventThing(); + _fixture = new Fixture(); + } + + [Fact] + public void TryAddWhenSetThingTypeIsNotCalled() + => Assert.Throws(() => _builder.Add(Substitute.For(), null)); + + [Fact] + public void TryAddWhenSetThingOptionIsNotCalled() + { + _builder.SetThingType(_thing.GetType()); + Assert.Throws(() => _builder.Add(Substitute.For(), null)); + } + + [Fact] + public void TryBuildWhenIsNotSetSetThing() + => Assert.Throws(() => _builder.Build()); + + [Fact] + public void TryBuildWhenIsNotSetThingType() + { + _builder.SetThing(_thing); + Assert.Throws(() => _builder.Build()); + } + + [Fact] + public void BuildEventsAndInvokeInt() + { + _builder + .SetThing(_thing) + .SetThingType(_thing.GetType()) + .SetThingOption(new ThingOption()); + + Visit(); + + var events = _builder.Build(); + events.Should().NotBeEmpty(); + events.Should().HaveCount(2); + events.Should().ContainKey(nameof(EventThing.Int)); + + _thing.ThingContext = new ThingContext( + new Dictionary(), + events, + new Dictionary(), + new Dictionary()); + + var value = _fixture.Create(); + _thing.Invoke(value); + + + var array = events[nameof(EventThing.Int)].ToArray(); + array.Should().NotBeEmpty(); + array.Should().HaveCount(1); + array[0].Data.Should().Be(value); + } + + [Fact] + public void BuildEventsWithCustomNameAndInvokeInt() + { + _builder + .SetThing(_thing) + .SetThingType(_thing.GetType()) + .SetThingOption(new ThingOption()); + + Visit(); + + var events = _builder.Build(); + events.Should().NotBeEmpty(); + events.Should().HaveCount(2); + events.Should().ContainKey("test"); + + _thing.ThingContext = new ThingContext( + new Dictionary(), + events, + new Dictionary(), + new Dictionary()); + + var value = _fixture.Create(); + _thing.Invoke(value); + + var array = events["test"].ToArray(); + array.Should().NotBeEmpty(); + array.Should().HaveCount(1); + array[0].Data.Should().Be(value); + } + + private void Visit() + { + var events = _thing.GetType().GetEvents(BindingFlags.Public | BindingFlags.Instance); + + foreach (var @event in events) + { + var args = @event.EventHandlerType!.GetGenericArguments(); + if (args.Length > 1) + { + continue; + } + + if ((args.Length == 0 && @event.EventHandlerType != typeof(EventHandler)) + || (args.Length == 1 && @event.EventHandlerType != typeof(EventHandler<>).MakeGenericType(args[0]))) + { + continue; + } + + _builder.Add(@event, @event.GetCustomAttribute()); + } + } + + public class EventThing : Thing + { + public override string Name => "event-thing"; + + public event EventHandler Int; + + [ThingEvent(Name = "test")] + public event EventHandler String; + + public void Invoke(int value) + => Int?.Invoke(this, value); + + public void Invoke(string value) + => String?.Invoke(this, value); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Builder/PropertyBuilderTest.cs b/test/Mozilla.IoT.WebThing.Test/Builder/PropertyBuilderTest.cs new file mode 100644 index 0000000..9719da3 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Builder/PropertyBuilderTest.cs @@ -0,0 +1,143 @@ +using System; +using System.Linq; +using System.Reflection; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; +using Mozilla.IoT.WebThing.Properties; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Builder +{ + public class PropertyBuilderTest + { + private readonly PropertyBuilder _builder; + private readonly PropertyThing _thing; + private readonly FakePropertyFactory _factory; + private readonly Fixture _fixture; + + public PropertyBuilderTest() + { + _fixture = new Fixture(); + _thing = new PropertyThing(); + + _factory = new FakePropertyFactory(); + _builder = new PropertyBuilder(_factory); + } + + [Fact] + public void TryAddWhenSetThingTypeIsNotCalled() + => Assert.Throws(() => _builder.Add(Substitute.For(), + new Information(null, null, null, null, null, + null, null, null, null, _fixture.Create(), + _fixture.Create(), _fixture.Create()))); + + [Fact] + public void TryBuildWhenIsNotSetSetThing() + => Assert.Throws(() => _builder.Build()); + + [Fact] + public void TryBuildWhenIsNotSetThingType() + { + _builder.SetThing(_thing); + Assert.Throws(() => _builder.Build()); + } + + [Fact] + public void BuildReadOnlyProperty() + { + _builder + .SetThing(_thing) + .SetThingOption(new ThingOption()); + + var propertyName = _fixture.Create(); + + Visit(new Information(null, null, null, null, null, + null, null, null, null, true, + propertyName, _fixture.Create())); + + var properties = _builder.Build(); + properties.Should().NotBeNull(); + properties.Should().NotBeEmpty(); + properties.Should().HaveCount(1); + properties.Should().ContainKey(propertyName); + properties[propertyName].Should().BeAssignableTo(); + _thing.Value = _fixture.Create(); + properties[propertyName].GetValue().Should().Be(_thing.Value); + } + + [Fact] + public void BuildNonReadOnlyProperty() + { + _builder + .SetThing(_thing) + .SetThingOption(new ThingOption()); + + var propertyName = _fixture.Create(); + + var information = new Information(null, null, null, null, null, + null, null, null, null, false, + propertyName, _fixture.Create()); + + Visit(information); + + var properties = _builder.Build(); + properties.Should().NotBeNull(); + properties.Should().NotBeEmpty(); + properties.Should().HaveCount(1); + properties.Should().ContainKey(propertyName); + properties[propertyName].Should().NotBeAssignableTo(); + + _factory.Information.Should().Be(information); + var value = _fixture.Create(); + _factory.Setter(_thing, value); + _factory.Getter(_thing).Should().Be(value); + _thing.Value.Should().Be(value); + } + + private void Visit(Information information) + { + var properties = _thing.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => !IsThingProperty(x.Name)); + + foreach (var property in properties) + { + _builder.Add(property, information); + } + + static bool IsThingProperty(string name) + => name == nameof(Thing.Context) + || name == nameof(Thing.Name) + || name == nameof(Thing.Description) + || name == nameof(Thing.Title) + || name == nameof(Thing.Type) + || name == nameof(Thing.ThingContext); + } + + private class FakePropertyFactory : IPropertyFactory + { + public Information Information { get; private set; } + public Action Setter { get; private set; } + public Func Getter { get; private set; } + public IProperty Create(Type propertyType, Information information, Thing thing, Action setter, + Func getter) + { + Information = information; + Setter = setter; + Getter = getter; + + return Substitute.For(); + } + } + + public class PropertyThing : Thing + { + public override string Name => "property-thing"; + + public string Value { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Builder/ThingResponseBuilderTest.cs b/test/Mozilla.IoT.WebThing.Test/Builder/ThingResponseBuilderTest.cs new file mode 100644 index 0000000..5b7e9ee --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Builder/ThingResponseBuilderTest.cs @@ -0,0 +1,914 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Extensions; +using Newtonsoft.Json.Linq; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Builder +{ + public class ThingResponseBuilderTest + { + private readonly ThingOption _option; + private readonly ThingResponseBuilder _builder; + private readonly Fixture _fixture; + + public ThingResponseBuilderTest() + { + _builder = new ThingResponseBuilder(); + _fixture = new Fixture(); + _option = new ThingOption + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + } + + [Fact] + public void TryAddWhenSetThingIsNotCalled() + => Assert.Throws(() => _builder.Add(Substitute.For(), null)); + + [Fact] + public void TryBuildWhenSetThingIsNotCalled() + => Assert.Throws(() => _builder.Build()); + + [Fact] + public void BuildWithEvent() + { + var thing = new EventThing(); + _builder + .SetThing(thing) + .SetThingOption(_option); + + Visit(thing.GetType()); + + var response = _builder.Build(); + response.Should().NotBeNull(); + + var message = JsonSerializer.Serialize(response, response.GetType(), _option.ToJsonSerializerOptions()); + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""events"": { + ""int"": { + ""link"": [ + { + ""href"": ""/things/event-thing/events/int"", + ""rel"": ""event"" + } + ] + }, + ""test"": { + ""title"": ""Bar"", + ""description"": ""Foo"", + ""unit"": ""milli"", + ""link"": [ + { + ""href"": ""/things/event-thing/events/test"", + ""rel"": ""event"" + } + ] + } + }, + ""@context"": ""https://iot.mozilla.org/schemas"", + ""links"": [{ + ""href"": ""properties"", + ""rel"": ""/things/event-thing/properties"" + },{ + ""href"": ""events"", + ""rel"": ""/things/event-thing/events"" + },{ + ""href"": ""actions"", + ""rel"": ""/things/event-thing/actions"" + }] +} +")); + + void Visit(Type thingType) + { + var events = thingType.GetEvents(BindingFlags.Public | BindingFlags.Instance); + + foreach (var @event in events) + { + var args = @event.EventHandlerType!.GetGenericArguments(); + if (args.Length > 1) + { + continue; + } + + if ((args.Length == 0 && @event.EventHandlerType != typeof(EventHandler)) + || (args.Length == 1 && @event.EventHandlerType != typeof(EventHandler<>).MakeGenericType(args[0]))) + { + continue; + } + + _builder.Add(@event, @event.GetCustomAttribute()); + } + } + } + + [Fact] + public void BuildWithProperties() + { + var thing = new PropertyThing(); + _builder + .SetThing(thing) + .SetThingOption(_option); + + Visit(thing.GetType()); + + var response = _builder.Build(); + response.Should().NotBeNull(); + + var message = JsonSerializer.Serialize(response, response.GetType(), _option.ToJsonSerializerOptions()); + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""properties"": { + ""bool"": { + ""type"": ""boolean"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/bool"", + ""rel"": ""property"" + } + ] + }, + ""guid"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/guid"", + ""rel"": ""property"" + } + ] + }, + ""timeSpan"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/timeSpan"", + ""rel"": ""property"" + } + ] + }, + ""dateTime"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/dateTime"", + ""rel"": ""property"" + } + ] + }, + ""dateTimeOffset"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/dateTimeOffset"", + ""rel"": ""property"" + } + ] + }, + ""enum"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/enum"", + ""rel"": ""property"" + } + ] + }, + ""string"": { + ""type"": ""string"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/string"", + ""rel"": ""property"" + } + ] + }, + ""byte"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/byte"", + ""rel"": ""property"" + } + ] + }, + ""sbyte"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/sbyte"", + ""rel"": ""property"" + } + ] + }, + ""short"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/short"", + ""rel"": ""property"" + } + ] + }, + ""ushort"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/ushort"", + ""rel"": ""property"" + } + ] + }, + ""int"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/int"", + ""rel"": ""property"" + } + ] + }, + ""uint"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/uint"", + ""rel"": ""property"" + } + ] + }, + ""long"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/long"", + ""rel"": ""property"" + } + ] + }, + ""ulong"": { + ""type"": ""integer"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/ulong"", + ""rel"": ""property"" + } + ] + }, + ""float"": { + ""type"": ""number"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/float"", + ""rel"": ""property"" + } + ] + }, + ""double"": { + ""type"": ""number"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/double"", + ""rel"": ""property"" + } + ] + }, + ""decimal"": { + ""type"": ""number"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/decimal"", + ""rel"": ""property"" + } + ] + } + }, + ""@context"": ""https://iot.mozilla.org/schemas"", + ""links"": [{ + ""href"": ""properties"", + ""rel"": ""/things/property-thing/properties"" + },{ + ""href"": ""events"", + ""rel"": ""/things/property-thing/events"" + },{ + ""href"": ""actions"", + ""rel"": ""/things/property-thing/actions"" + }] +} +")); + + void Visit(Type thingType) + { + var properties = thingType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => !IsThingProperty(x.Name)); + + foreach (var property in properties) + { + _builder.Add(property, null, + new Information(null, null, null, null, null, + null, null, null, null, false, + property.Name, _fixture.Create())); + } + } + + static bool IsThingProperty(string name) + => name == nameof(Thing.Context) + || name == nameof(Thing.Name) + || name == nameof(Thing.Description) + || name == nameof(Thing.Title) + || name == nameof(Thing.Type) + || name == nameof(Thing.ThingContext); + } + + [Fact] + public void BuildWithPropertiesInformation() + { + var thing = new PropertyThing(); + + _builder + .SetThing(thing) + .SetThingOption(_option); + + Visit(thing.GetType()); + + var response = _builder.Build(); + response.Should().NotBeNull(); + + var message = JsonSerializer.Serialize(response, response.GetType(), _option.ToJsonSerializerOptions()); + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""links"": [{ + ""href"": ""properties"", + ""rel"": ""/things/property-thing/properties"" + },{ + ""href"": ""events"", + ""rel"": ""/things/property-thing/events"" + },{ + ""href"": ""actions"", + ""rel"": ""/things/property-thing/actions"" + }], + ""properties"": { + ""bool2"": { + ""title"": ""Boo Title"", + ""description"": ""Bool test"", + ""type"": ""boolean"", + ""isReadOnly"": false, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/bool2"", + ""rel"": ""property"" + } + ] + }, + ""guid2"": { + ""type"": ""string"", + ""isReadOnly"": true, + ""link"": [ + { + ""href"": ""/things/property-thing/properties/guid2"", + ""rel"": ""property"" + } + ] + }, + ""string2"": { + ""title"": ""String title"", + ""description"": ""String Description"", + ""@type"": [""ABC"",""DEF""], + ""type"": ""string"", + ""isReadOnly"": false, + ""minimumLength"": 1, + ""maximumLength"": 100, + ""pattern"": ""^([a-zA-Z0-9_\\-\\.]\u002B)@([a-zA-Z0-9_\\-\\.]\u002B)\\.([a-zA-Z]{2,5})$"", + ""enums"": [ ""test@outlook.com"", ""test@gmail.com"", ""test@tese.com""], + ""link"": [ + { + ""href"": ""/things/property-thing/properties/string2"", + ""rel"": ""property"" + } + ] + }, + ""int2"": { + ""title"": ""Int title"", + ""description"": ""int Description"", + ""@type"": ""ABC"", + ""type"": ""integer"", + ""isReadOnly"": false, + ""minimum"": 1, + ""maximum"": 100, + ""enums"": [1, 2, 3], + ""link"": [ + { + ""href"": ""/things/property-thing/properties/int2"", + ""rel"": ""property"" + } + ] + }, + ""double2"": { + ""title"": ""Double title"", + ""description"": ""Double Description"", + ""@type"": ""ABC"", + ""type"": ""number"", + ""isReadOnly"": false, + ""exclusiveMinimum"": 1, + ""exclusiveMaximum"": 100, + ""enums"": [1.1, 2.3 ,3], + ""link"": [ + { + ""href"": ""/things/property-thing/properties/double2"", + ""rel"": ""property"" + } + ] + } + } +} +")); + + void Visit(Type thingType) + { + var p = new[] { + nameof(PropertyThing.Bool), + nameof(PropertyThing.Guid), + nameof(PropertyThing.String), + nameof(PropertyThing.Int), + nameof(PropertyThing.Double) + }; + var properties = thingType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => p.Contains(x.Name)); + + foreach (var property in properties) + { + var attribute = property.GetCustomAttribute(); + _builder.Add(property, attribute, ToInformation(attribute)); + } + } + + static Information ToInformation(ThingPropertyAttribute attribute) + { + return new Information(attribute.MinimumValue, attribute.MaximumValue, + attribute.ExclusiveMinimumValue, attribute.ExclusiveMaximumValue, + attribute.MultipleOfValue, attribute.MinimumLengthValue, + attribute.MaximumLengthValue, attribute.Pattern, attribute.Enum, + attribute.IsReadOnly, attribute.Name!, false); + } + } + + [Fact] + public void BuildWithActions() + { + var thing = new ActionThing(); + + _builder + .SetThing(thing) + .SetThingOption(_option); + + Visit(thing.GetType()); + + var response = _builder.Build(); + response.Should().NotBeNull(); + + var message = JsonSerializer.Serialize(response, response.GetType(), _option.ToJsonSerializerOptions()); + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""links"": [{ + ""href"": ""properties"", + ""rel"": ""/things/action-thing/properties"" + },{ + ""href"": ""events"", + ""rel"": ""/things/action-thing/events"" + },{ + ""href"": ""actions"", + ""rel"": ""/things/action-thing/actions"" + }], + ""actions"": { + ""noParameter"": { + ""link"": [ + { + ""href"": ""/things/action-thing/actions/noParameter"", + ""rel"": ""action"" + } + ], + ""input"": { + ""type"": ""object"", + ""properties"": {} + } + }, + ""withParameter"": { + ""link"": [ + { + ""href"": ""/things/action-thing/actions/withParameter"", + ""rel"": ""action"" + } + ], + ""input"": { + ""type"": ""object"", + ""properties"": { + ""bool"": { + ""type"": ""boolean"" + }, + ""guid"": { + ""type"": ""string"" + }, + ""timeSpan"": { + ""type"": ""string"" + }, + ""dateTime"": { + ""type"": ""string"" + }, + ""dateTimeOffset"": { + ""type"": ""string"" + }, + ""foo"": { + ""type"": ""string"" + }, + ""string"": { + ""type"": ""string"" + }, + ""byte"": { + ""type"": ""integer"" + }, + ""sbyte"": { + ""type"": ""integer"" + }, + ""short"": { + ""type"": ""integer"" + }, + ""ushort"": { + ""type"": ""integer"" + }, + ""int"": { + ""type"": ""integer"" + }, + ""uint"": { + ""type"": ""integer"" + }, + ""long"": { + ""type"": ""integer"" + }, + ""ulong"": { + ""type"": ""integer"" + }, + ""float"": { + ""type"": ""number"" + }, + ""double"": { + ""type"": ""number"" + }, + ""decimal"": { + ""type"": ""number"" + } + } + } + } + } + } +")); + + void Visit(Type thingType) + { + var methods = thingType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where( x => !x.IsSpecialName + && x.Name != nameof(Equals) && x.Name != nameof(GetType) + && x.Name != nameof(GetHashCode) && x.Name != nameof(ToString)); + + foreach (var method in methods) + { + _builder.Add(method, null); + + foreach (var parameter in method.GetParameters()) + { + _builder.Add(parameter, null, new Information(null, null, null, null, null, + null, null, null, null, false, + parameter.Name!, _fixture.Create())); + } + } + } + } + + [Fact] + public void BuildWithActionsWithInformation() + { + var thing = new ActionThing(); + + _builder + .SetThing(thing) + .SetThingOption(_option); + + Visit(thing.GetType()); + + var response = _builder.Build(); + response.Should().NotBeNull(); + + var message = JsonSerializer.Serialize(response, response.GetType(), _option.ToJsonSerializerOptions()); + FluentAssertions.Json.JsonAssertionExtensions.Should(JToken.Parse(message)) + .BeEquivalentTo(JToken.Parse(@" +{ + ""@context"": ""https://iot.mozilla.org/schemas"", + ""links"": [{ + ""href"": ""properties"", + ""rel"": ""/things/action-thing/properties"" + },{ + ""href"": ""events"", + ""rel"": ""/things/action-thing/events"" + },{ + ""href"": ""actions"", + ""rel"": ""/things/action-thing/actions"" + }], + ""actions"": { + ""test"": { + ""title"": ""Ola"", + ""description"": ""teste 2"", + ""link"": [ + { + ""href"": ""/things/action-thing/actions/test"", + ""rel"": ""action"" + } + ], + ""input"": { + ""@type"": ""ABC"", + ""type"": ""object"", + ""properties"": {} + } + }, + ""withParameter"": { + ""link"": [ + { + ""href"": ""/things/action-thing/actions/withParameter"", + ""rel"": ""action"" + } + ], + ""input"": { + ""@type"": [ + ""ABC"", + ""DEF"" + ], + ""type"": ""object"", + ""properties"": { + ""bool2"": { + ""title"": ""Boo Title"", + ""description"": ""Bool test"", + ""type"": ""boolean"" + }, + ""guid2"": { + ""type"": ""string"" + }, + ""timeSpan"": { + ""type"": ""string"" + }, + ""dateTime"": { + ""type"": ""string"" + }, + ""dateTimeOffset"": { + ""type"": ""string"" + }, + ""foo"": { + ""type"": ""string"" + }, + ""string2"": { + ""title"": ""String title"", + ""description"": ""String Description"", + ""type"": ""string"", + ""minimumLength"": 1, + ""maximumLength"": 100, + ""pattern"": ""^([a-zA-Z0-9_\\-\\.]\u002B)@([a-zA-Z0-9_\\-\\.]\u002B)\\.([a-zA-Z]{2,5})$"", + ""enums"": [ + ""test@outlook.com"", + ""test@gmail.com"", + ""test@tese.com"" + ] + }, + ""byte"": { + ""type"": ""integer"" + }, + ""sbyte"": { + ""type"": ""integer"" + }, + ""short"": { + ""type"": ""integer"" + }, + ""ushort"": { + ""type"": ""integer"" + }, + ""int2"": { + ""title"": ""Int title"", + ""description"": ""int Description"", + ""type"": ""integer"", + ""minimum"": 1, + ""maximum"": 100, + ""enums"": [ + 1, + 2, + 3 + ] + }, + ""uint"": { + ""type"": ""integer"" + }, + ""long"": { + ""type"": ""integer"" + }, + ""ulong"": { + ""type"": ""integer"" + }, + ""float"": { + ""type"": ""number"" + }, + ""double2"": { + ""title"": ""Double title"", + ""description"": ""Double Description"", + ""type"": ""number"", + ""exclusiveMinimum"": 1, + ""exclusiveMaximum"": 100, + ""enums"": [ + 1.1, + 2.3, + 3 + ] + }, + ""decimal"": { + ""type"": ""number"" + } + } + } + } + } + } +")); + + void Visit(Type thingType) + { + var methods = thingType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where( x => !x.IsSpecialName + && x.Name != nameof(Equals) && x.Name != nameof(GetType) + && x.Name != nameof(GetHashCode) && x.Name != nameof(ToString)); + + foreach (var method in methods) + { + _builder.Add(method, method.GetCustomAttribute()); + + foreach (var parameter in method.GetParameters()) + { + _builder.Add(parameter, parameter.GetCustomAttribute(), + ToInformation(parameter.GetCustomAttribute(), + parameter.Name)); + } + } + } + + Information ToInformation(ThingParameterAttribute attribute, string name) + { + return new Information(attribute?.MinimumValue, attribute?.MaximumValue, attribute?.ExclusiveMinimumValue, + attribute?.ExclusiveMaximumValue, attribute?.MultipleOfValue, attribute?.MinimumLengthValue, + attribute?.MaximumLengthValue, attribute?.Pattern, attribute?.Enum, false, + attribute?.Name ?? name, _fixture.Create()); + } + } + + public class EventThing : Thing + { + public override string Name => "event-thing"; + + public event EventHandler Int; + + [ThingEvent(Name = "Test", Description = "Foo", Title = "Bar", Unit = "milli")] + public event EventHandler String; + } + + public class PropertyThing : Thing + { + public override string Name => "property-thing"; + + [ThingProperty(Name = "bool2", + Title = "Boo Title", + Description = "Bool test")] + public bool Bool { get; set; } + + #region String + [ThingProperty(Name = "Guid2", IsReadOnly = true)] + public Guid Guid { get; set; } + public TimeSpan TimeSpan { get; set; } + public DateTime DateTime { get; set; } + public DateTimeOffset DateTimeOffset { get; set; } + public Foo Enum { get; set; } + + [ThingProperty(Name = "String2", + Title = "String title", + Description = "String Description", + MinimumLength = 1, + MaximumLength = 100, + Pattern = @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$", + Enum = new object[]{ "test@outlook.com", "test@gmail.com", "test@tese.com" }, + Type = new[] { "ABC", "DEF"})] + public string String { get; set; } + #endregion + + #region Number + public byte Byte { get; set; } + public sbyte Sbyte { get; set; } + public short Short { get; set; } + public ushort Ushort { get; set; } + + [ThingProperty(Name = "Int2", + Title = "Int title", + Description = "int Description", + Minimum = 1, + Maximum = 100, + Enum = new object[]{ 1, 2, 3 }, + Type = new[] { "ABC" })] + public int Int { get; set; } + public uint Uint { get; set; } + public long Long { get; set; } + public ulong Ulong { get; set; } + public float Float { get; set; } + + [ThingProperty(Name = "Double2", + Title = "Double title", + Description = "Double Description", + ExclusiveMinimum = 1, + ExclusiveMaximum = 100, + Enum = new object[]{ 1.1, 2.3, 3 }, + Type = new[] { "ABC" })] + public double Double { get; set; } + public decimal Decimal { get; set; } + #endregion + } + + public class ActionThing : Thing + { + public override string Name => "action-thing"; + + [ThingAction(Name = "test", Description = "teste 2", Title = "Ola", Type = new []{ "ABC" })] + public void NoParameter() + { + + } + + [ThingAction(Type = new []{ "ABC", "DEF" })] + public void WithParameter( + [ThingParameter(Name = "bool2", Title = "Boo Title", Description = "Bool test")]bool @bool, + [ThingParameter(Name = "Guid2")]Guid guid, + TimeSpan timeSpan, + DateTime dateTime, + DateTimeOffset dateTimeOffset, + Foo foo, + [ThingParameter(Name = "String2", Title = "String title", Description = "String Description", + MinimumLength = 1, MaximumLength = 100, Pattern = @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$", + Enum = new object[]{ "test@outlook.com", "test@gmail.com", "test@tese.com" })]string @string, + byte @byte, + sbyte @sbyte, + short @short, + ushort @ushort, + [ThingParameter(Name = "Int2", Title = "Int title", Description = "int Description", + Minimum = 1, Maximum = 100, Enum = new object[]{ 1, 2, 3 })]int @int, + uint @uint, + long @long, + ulong @ulong, + float @float, + [ThingParameter(Name = "Double2", Title = "Double title", Description = "Double Description", + ExclusiveMinimum = 1, ExclusiveMaximum = 100, Enum = new object[]{ 1.1, 2.3, 3 })]double @double, + decimal @decimal + ) + { + } + } + + public enum Foo + { + A, + Bar, + C + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/EventCollectionTest.cs b/test/Mozilla.IoT.WebThing.Test/Events/EventCollectionTest.cs similarity index 97% rename from test/Mozilla.IoT.WebThing.Test/EventCollectionTest.cs rename to test/Mozilla.IoT.WebThing.Test/Events/EventCollectionTest.cs index 28fe18a..2fb31c7 100644 --- a/test/Mozilla.IoT.WebThing.Test/EventCollectionTest.cs +++ b/test/Mozilla.IoT.WebThing.Test/Events/EventCollectionTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AutoFixture; using FluentAssertions; +using Mozilla.IoT.WebThing.Events; using Xunit; namespace Mozilla.IoT.WebThing.Test diff --git a/test/Mozilla.IoT.WebThing.Test/Extensions/FixtureExtensions.cs b/test/Mozilla.IoT.WebThing.Test/Extensions/FixtureExtensions.cs new file mode 100644 index 0000000..76e7335 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Extensions/FixtureExtensions.cs @@ -0,0 +1,83 @@ +using System; +using AutoFixture; + +namespace Mozilla.IoT.WebThing.Test.Extensions +{ + public static class FixtureExtensions + { + public static object GetValue(this Fixture fixture, Type type) + { + if (type == typeof(bool)) + { + return fixture.Create(); + } + + if (type == typeof(byte)) + { + return fixture.Create(); + } + + if (type == typeof(sbyte)) + { + return fixture.Create(); + } + + if (type == typeof(short)) + { + return fixture.Create(); + } + + if (type == typeof(ushort)) + { + return fixture.Create(); + } + + if (type == typeof(int)) + { + return fixture.Create(); + } + + if (type == typeof(uint)) + { + return fixture.Create(); + } + + if (type == typeof(long)) + { + return fixture.Create(); + } + + if (type == typeof(ulong)) + { + return fixture.Create(); + } + + if (type == typeof(float)) + { + return fixture.Create(); + } + + if (type == typeof(double)) + { + return fixture.Create(); + } + + if (type == typeof(decimal)) + { + return fixture.Create(); + } + + if (type == typeof(DateTime)) + { + return fixture.Create(); + } + + if (type == typeof(DateTimeOffset)) + { + return fixture.Create(); + } + + return fixture.Create(); + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Factory/ThingContextFactoryTest.cs b/test/Mozilla.IoT.WebThing.Test/Factory/ThingContextFactoryTest.cs new file mode 100644 index 0000000..60f8861 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Factory/ThingContextFactoryTest.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Mozilla.IoT.WebThing.Actions; +using Mozilla.IoT.WebThing.Attributes; +using Mozilla.IoT.WebThing.Builders; +using Mozilla.IoT.WebThing.Events; +using Mozilla.IoT.WebThing.Extensions; +using Mozilla.IoT.WebThing.Factories; +using Mozilla.IoT.WebThing.Properties; +using NSubstitute; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Factory +{ + public class ThingContextFactoryTest + { + private readonly ThingContextFactory _factory; + private readonly IThingResponseBuilder _response; + private readonly IActionBuilder _action; + private readonly IEventBuilder _event; + private readonly IPropertyBuilder _property; + + public ThingContextFactoryTest() + { + _response = Substitute.For(); + _event = Substitute.For(); + _property = Substitute.For(); + _action = Substitute.For(); + + _factory = new ThingContextFactory(_event, _property, _response, _action); + + _event + .Build() + .Returns(new Dictionary()); + + _response + .Build() + .Returns(new Dictionary()); + + _action + .Build() + .Returns(new Dictionary()); + + _property + .Build() + .Returns(new Dictionary()); + } + + [Fact] + public void CreateWithEvent() + { + var thing = new EventThing(); + var option = new ThingOption(); + + var context = _factory.Create(thing, option); + + context.Should().NotBeNull(); + + _event + .Received(1) + .Add(Arg.Any(), Arg.Any()); + + _response + .Received(1) + .Add(Arg.Any(), Arg.Any()); + + _event + .Received(1) + .Build(); + + _property + .Received(1) + .Build(); + + _action + .Received(1) + .Build(); + + _response + .Received(1) + .Build(); + + _property + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _action + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _action + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + } + + [Fact] + public void CreateWithProperty() + { + var thing = new PropertyThing(); + var option = new ThingOption(); + + var context = _factory.Create(thing, option); + + context.Should().NotBeNull(); + + _property + .Received(1) + .Build(); + + _action + .Received(1) + .Build(); + + _response + .Received(1) + .Build(); + + _event + .Received(1) + .Build(); + + _property + .Received(5) + .Add(Arg.Any(), Arg.Any()); + + _response + .Received(5) + .Add(Arg.Any(), Arg.Any(), Arg.Any()); + + _event + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _action + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _action + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + } + + [Fact] + public void CreateWithActions() + { + var thing = new ActionThing(); + var option = new ThingOption(); + + var context = _factory.Create(thing, option); + + context.Should().NotBeNull(); + + _property + .Received(1) + .Build(); + + _action + .Received(1) + .Build(); + + _response + .Received(1) + .Build(); + + _event + .Received(1) + .Build(); + + _property + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _response + .DidNotReceive() + .Add(Arg.Any(), Arg.Any(), Arg.Any()); + + _event + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _response + .DidNotReceive() + .Add(Arg.Any(), Arg.Any()); + + _action + .Received(2) + .Add(Arg.Any(), Arg.Any()); + + _action + .Received(2) + .Add(Arg.Any(), Arg.Any()); + + _response + .Received(2) + .Add(Arg.Any(), Arg.Any()); + + _response + .Received(2) + .Add(Arg.Any(), Arg.Any(), Arg.Any()); + } + + public class EventThing : Thing + { + public delegate void NotValidHandler(object? sender); + public override string Name => "event-thing"; + + public event EventHandler Int; + + [ThingEvent(Ignore = true)] + public event EventHandler Ignore; + + public event NotValidHandler NotValid; + } + + public class PropertyThing : Thing + { + public override string Name => "property-thing"; + + public PropertyThing() + { + ReadOnly3 = Guid.NewGuid(); + } + + [ThingProperty(Name = "bool2")] + public bool? Bool { get; set; } + + [ThingProperty(Ignore = true)] + public Guid Ignore { get; set; } + + [ThingProperty(IsReadOnly = true)] + public Guid ReadOnly { get; set; } + + public Guid ReadOnly2 { get; private set; } + public Guid ReadOnly3 { get; } + + public string Value { get; set; } + } + + public class ActionThing : Thing + { + public override string Name => "action-thing"; + + [ThingAction(Ignore = true)] + public void Ignore() + { + + } + + + [ThingAction(Name = "test")] + public void Some() + { + + } + + public void Some2( + [ThingParameter(Name = "something")]string @string, + bool? enable, + [FromServices]object other, + CancellationToken cancellationToken) + { + + } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Generator/ActionInterceptFactoryTest.cs b/test/Mozilla.IoT.WebThing.Test/Generator/ActionInterceptFactoryTest.cs deleted file mode 100644 index 033c6d5..0000000 --- a/test/Mozilla.IoT.WebThing.Test/Generator/ActionInterceptFactoryTest.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using AutoFixture; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Mozilla.IoT.WebThing.Actions; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Converts; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories; -using Mozilla.IoT.WebThing.Factories.Generator.Actions; -using NSubstitute; -using Xunit; - -namespace Mozilla.IoT.WebThing.Test.Generator -{ - public class ActionInterceptFactoryTest - { - private readonly Fixture _fixture; - private readonly LampThing _thing; - private readonly ActionInterceptFactory _factory; - private readonly IServiceProvider _provider; - - - public ActionInterceptFactoryTest() - { - _fixture = new Fixture(); - _thing = new LampThing(); - _factory = new ActionInterceptFactory(new ThingOption()); - var logger = Substitute.For>(); - _provider = Substitute.For(); - - _provider.GetService(typeof(ILogger)) - .Returns(logger); - } - - [Fact] - public void Ignore() - { - CodeGeneratorFactory.Generate(_thing, new[] {_factory}); - _factory.Actions.Should().NotContainKey(nameof(LampThing.Ignore)); - } - - - private ActionInfo CreateAction(string actionName) - { - CodeGeneratorFactory.Generate(_thing, new[] {_factory}); - _factory.Actions.Should().ContainKey(actionName); - - _thing.ThingContext = new Context(Substitute.For(), - Substitute.For(), - new Dictionary(), - _factory.Actions); - var actionType = _thing.ThingContext.Actions[actionName].ActionType; - return (ActionInfo)Activator.CreateInstance(actionType); - - } - - private ActionInfo CreateAction(string actionName, int inputValue) - { - CodeGeneratorFactory.Generate(_thing, new[] {_factory}); - _factory.Actions.Should().ContainKey(actionName); - - _thing.ThingContext = new Context(Substitute.For(), - Substitute.For(), - new Dictionary(), - _factory.Actions); - var actionType = _thing.ThingContext.Actions[actionName].ActionType; - var action = (ActionInfo)Activator.CreateInstance(actionType); - - var inputPropertyType = actionType.GetProperty("Input", BindingFlags.Public | BindingFlags.Instance); - inputPropertyType.Should().NotBeNull(); - - var input = Activator.CreateInstance(inputPropertyType.PropertyType); - var valueProperty = inputPropertyType.PropertyType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance); - valueProperty.Should().NotBeNull(); - - valueProperty.SetValue(input, inputValue); - inputPropertyType.SetValue(action, input); - return action; - } - - #region Void - - [Fact] - public async Task VoidWithoutParameter() - { - var action = CreateAction(nameof(LampThing.ReturnVoid)); - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(1); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnVoid)); - } - - [Fact] - public async Task VoidWithParameter() - { - var value = _fixture.Create(); - var action = CreateAction(nameof(LampThing.ReturnVoidWithParameter), value); - - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(2); - _thing.Values.First.Value.Should().Be(value.ToString()); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnVoidWithParameter)); - } - - [Theory] - [InlineData(0)] - [InlineData(50)] - [InlineData(100)] - public async Task VoidWithParameterValid(int value) - { - var action = CreateAction(nameof(LampThing.ReturnVoidWithParameterWithValidation), value); - - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(2); - _thing.Values.First.Value.Should().Be(value.ToString()); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnVoidWithParameterWithValidation)); - } - - [Theory] - [InlineData(-1)] - [InlineData(101)] - public void VoidWithParameterInvalid(int value) - { - var action = CreateAction(nameof(LampThing.ReturnVoidWithParameterWithValidation), value); - - action.IsValid().Should().BeFalse(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - _thing.Values.Should().BeEmpty(); - } - - [Fact] - public async Task Throwable() - { - var action = CreateAction(nameof(LampThing.Throwable)); - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(1); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.Throwable)); - } - - [Fact] - public async Task FromService() - { - var value = 10; - var action = CreateAction(nameof(LampThing.ReturnParameterWithValidationAndParameterFromService), value); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.IsValid().Should().BeTrue(); - - var something = Substitute.For(); - - _provider.GetService(typeof(ISomething)) - .Returns(something); - - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(2); - _thing.Values.First.Value.Should().Be(value.ToString()); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnParameterWithValidationAndParameterFromService)); - - something - .Received(1) - .DoSomething(); - } - #endregion - - #region Task - [Fact] - public async Task TaskWithDelay() - { - var action = CreateAction(nameof(LampThing.ReturnTaskWithDelay)); - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(1); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnTaskWithDelay)); - - } - - [Fact] - public async Task TaskAction() - { - var action = CreateAction(nameof(LampThing.ReturnTask)); - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(1); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnTask)); - - } - - #endregion - - #region ValueTask - [Fact] - public async Task ValueTaskAction() - { - var action = CreateAction(nameof(LampThing.ReturnValueTask)); - action.IsValid().Should().BeTrue(); - - action.Status.Should().Be(Status.Pending.ToString().ToLower()); - action.TimeCompleted.Should().BeNull(); - await action.ExecuteAsync(_thing, _provider); - action.Status.Should().Be(Status.Completed.ToString().ToLower()); - action.TimeCompleted.Should().NotBeNull(); - - _thing.Values.Should().HaveCount(1); - _thing.Values.Last.Value.Should().Be(nameof(LampThing.ReturnValueTask)); - - } - #endregion - - public interface ISomething - { - void DoSomething(); - } - - public class LampThing : Thing - { - public override string Name => nameof(LampThing); - internal LinkedList Values { get; } = new LinkedList(); - - [ThingAction(Ignore = true)] - public void Ignore() - => Values.AddLast(nameof(Ignore)); - - #region Void - public void ReturnVoid() - => Values.AddLast(nameof(ReturnVoid)); - - public void ReturnVoidWithParameter(int value) - { - Values.AddLast(value.ToString()); - Values.AddLast(nameof(ReturnVoidWithParameter)); - } - - public void ReturnVoidWithParameterWithValidation([ThingParameter(Minimum = 0, Maximum = 100)]int value) - { - Values.AddLast(value.ToString()); - Values.AddLast(nameof(ReturnVoidWithParameterWithValidation)); - } - - public void ReturnParameterWithValidationAndParameterFromService([ThingParameter(Minimum = 0, Maximum = 100)]int value, - [FromServices]ISomething logger) - { - logger.DoSomething(); - Values.AddLast(value.ToString()); - Values.AddLast(nameof(ReturnParameterWithValidationAndParameterFromService)); - } - - public void Throwable() - { - Values.AddLast(nameof(Throwable)); - throw new Exception(); - } - #endregion - - #region Task - - public async Task ReturnTaskWithDelay() - { - await Task.Delay(100); - Values.AddLast(nameof(ReturnTaskWithDelay)); - } - - public Task ReturnTask() - { - return Task.Factory.StartNew(() => - { - Values.AddLast(nameof(ReturnTask)); - }); - } - #endregion - - #region ValueTask - public ValueTask ReturnValueTask() - { - return new ValueTask(Task.Factory.StartNew(() => - { - Values.AddLast(nameof(ReturnValueTask)); - })); - } - - #endregion - } - } -} diff --git a/test/Mozilla.IoT.WebThing.Test/Generator/ConverterInterceptorTest.cs b/test/Mozilla.IoT.WebThing.Test/Generator/ConverterInterceptorTest.cs deleted file mode 100644 index ab81316..0000000 --- a/test/Mozilla.IoT.WebThing.Test/Generator/ConverterInterceptorTest.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using AutoFixture; -using FluentAssertions.Json; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Converts; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories; -using Mozilla.IoT.WebThing.Factories.Generator.Converter; -using Newtonsoft.Json.Linq; -using NSubstitute; -using Xunit; - -namespace Mozilla.IoT.WebThing.Test.Generator -{ - public class ConverterInterceptorTest - { - private readonly Fixture _fixture; - private readonly LampThing _thing; - private readonly ConverterInterceptorFactory _factory; - - public ConverterInterceptorTest() - { - _fixture = new Fixture(); - _thing = new LampThing(); - _factory = new ConverterInterceptorFactory(_thing, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - IgnoreNullValues = true, - }); - } - - [Fact] - public void Serialize() - { - CodeGeneratorFactory.Generate(_thing, new[] {_factory}); - _thing.Prefix = new Uri("http://localhost/"); - _thing.ThingContext = new Context(_factory.Create(), - Substitute.For(), - new Dictionary(), - new Dictionary()); - - var value = JsonSerializer.Serialize(_thing, - new JsonSerializerOptions { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - IgnoreNullValues = true, - Converters = { new ThingConverter(new ThingOption()) } - }); - - JToken.Parse(value) - .Should() - .BeEquivalentTo(JToken.Parse(@" -{ - ""@context"": ""https://iot.mozilla.org/schemas"", - ""id"": ""http://localhost/things/lamp"", - ""title"": ""My Lamp"", - ""description"": ""A web connected lamp"", - ""@type"": [ - ""Light"", - ""OnOffSwitch"" - ], - ""properties"": { - ""on"": { - ""title"": ""On/Off"", - ""description"": ""Whether the lamp is turned on"", - ""readOnly"": false, - ""@type"": ""OnOffProperty"", - ""readOnly"": false, - ""type"": ""boolean"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/on"" - } - ] - }, - ""brightness"": { - ""title"": ""Brightness"", - ""description"": ""The level of light from 0-100"", - ""readOnly"": false, - ""@type"": ""BrightnessProperty"", - ""minimum"": 0, - ""maximum"": 100, - ""readOnly"": false, - ""type"": ""integer"", - ""links"": [ - { - ""href"": ""/things/lamp/properties/brightness"" - } - ] - } - }, - ""actions"": { - ""fade"": { - ""title"": ""Fade"", - ""description"": ""Fade the lamp to a given level"", - ""@type"": ""FadeAction"", - ""input"": { - ""type"": ""object"", - ""properties"": { - ""level"": { - ""type"": ""integer"", - ""minimum"": 0, - ""maximum"": 100 - }, - ""duration"": { - ""type"": ""integer"", - ""unit"": ""milliseconds"", - ""minimum"": 0 - } - } - }, - ""links"": [ - { - ""href"": ""/things/lamp/actions/fade"" - } - ] - } - }, - ""events"": { - ""overheated"": { - ""title"": ""Overheated"", - ""description"": ""The lamp has exceeded its safe operating temperature"", - ""@type"": ""OverheatedEvent"", - ""type"": ""number"", - ""links"": [ - { - ""href"": ""/things/lamp/events/overheated"" - } - ] - } - }, - ""links"": [ - { - ""rel"": ""properties"", - ""href"": ""/things/lamp/properties"" - }, - { - ""rel"": ""actions"", - ""href"": ""/things/lamp/actions"" - }, - { - ""rel"": ""events"", - ""href"": ""/things/lamp/events"" - }, - { - ""rel"": ""alternate"", - ""href"": ""ws://localhost/things/lamp"" - } - ] -} -")); - } - public class LampThing : Thing - { - public override string Name => "Lamp"; - public override string? Title => "My Lamp"; - public override string? Description => "A web connected lamp"; - public override string[]? Type { get; } = new[] { "Light", "OnOffSwitch" }; - - [ThingProperty(Ignore = true)] - public object Ignore { get; set; } - - [ThingProperty(Type = new []{ "OnOffProperty" }, Title = "On/Off", Description = "Whether the lamp is turned on")] - public bool On { get; set; } - - [ThingProperty(Type = new []{ "BrightnessProperty" },Title = "Brightness", - Description = "The level of light from 0-100", Minimum = 0, Maximum = 100)] - public int Brightness { get; set; } - - [ThingEvent(Ignore = true)] - public event EventHandler OnIgnore; - - [ThingEvent(Title = "Overheated", - Type = new [] {"OverheatedEvent"}, - Description = "The lamp has exceeded its safe operating temperature")] - public event EventHandler Overheated; - - - [ThingAction(Name = "fade", Title = "Fade", Type = new []{"FadeAction"}, - Description = "Fade the lamp to a given level")] - public void Fade( - [ThingParameter(Minimum = 0, Maximum = 100)]int level, - [ThingParameter(Minimum = 0, Unit = "milliseconds")]int duration) - { - Console.WriteLine("Fade executed...."); - } - - [ThingAction(Ignore = true)] - public void IgnoreA( - [ThingParameter(Minimum = 0, Maximum = 100)]int level, - [ThingParameter(Minimum = 0, Unit = "milliseconds")]int duration) - { - Console.WriteLine("Fade executed...."); - } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.Test/Generator/EventInterceptTest.cs b/test/Mozilla.IoT.WebThing.Test/Generator/EventInterceptTest.cs deleted file mode 100644 index c842bf4..0000000 --- a/test/Mozilla.IoT.WebThing.Test/Generator/EventInterceptTest.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using AutoFixture; -using FluentAssertions; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Converts; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories; -using Mozilla.IoT.WebThing.Factories.Generator.Events; -using NSubstitute; -using Xunit; - -namespace Mozilla.IoT.WebThing.Test.Generator -{ - public class EventInterceptTest - { - private readonly Fixture _fixture; - private readonly ThingOption _options; - - public EventInterceptTest() - { - _fixture = new Fixture(); - _options = new ThingOption(); - } - - - [Fact] - public void Valid() - { - var thing = new LampThing(); - var eventFactory = new EventInterceptFactory(thing, _options); - - CodeGeneratorFactory.Generate(thing, new []{ eventFactory }); - - thing.ThingContext = new Context(Substitute.For(), - Substitute.For(), - eventFactory.Events, - new Dictionary()); - - var @int = _fixture.Create(); - thing.Emit(@int); - var events = thing.ThingContext.Events[nameof(LampThing.Int)].ToArray(); - events.Should().HaveCount(1); - events[0].Data.Should().Be(@int); - - var @decimal = _fixture.Create(); - thing.Emit(@decimal); - events = thing.ThingContext.Events[nameof(LampThing.Decimal)].ToArray(); - events.Should().HaveCount(1); - events[0].Data.Should().Be(@decimal); - - thing.Emit((decimal?)null); - events = thing.ThingContext.Events[nameof(LampThing.Decimal)].ToArray(); - events.Should().HaveCount(2); - events[1].Data.Should().Be(null); - - var @dateTime = _fixture.Create(); - thing.Emit(dateTime); - events = thing.ThingContext.Events[nameof(LampThing.DateTime)].ToArray(); - events.Should().HaveCount(1); - events[0].Data.Should().Be(@dateTime); - - var @obj = _fixture.Create(); - thing.Emit(obj); - events = thing.ThingContext.Events[nameof(LampThing.Any)].ToArray(); - events.Should().HaveCount(1); - events[0].Data.Should().Be(@obj); - } - - [Fact] - public void InvalidEvent() - { - var thing = new LampThing(); - var eventFactory = new EventInterceptFactory(thing, _options); - - CodeGeneratorFactory.Generate(thing, new []{ eventFactory }); - - thing.ThingContext = new Context(Substitute.For(), - Substitute.For(), - eventFactory.Events, - new Dictionary()); - - var @int = _fixture.Create(); - thing.EmitInvalid(@int); - var events = thing.ThingContext.Events[nameof(LampThing.Int)].ToArray(); - events = thing.ThingContext.Events[nameof(LampThing.Int)].ToArray(); - events.Should().BeEmpty(); - } - - [Fact] - public void Ignore() - { - var thing = new LampThing(); - var eventFactory = new EventInterceptFactory(thing, _options); - - CodeGeneratorFactory.Generate(thing, new []{ eventFactory }); - - thing.ThingContext = new Context(Substitute.For(), - Substitute.For(), - eventFactory.Events, - new Dictionary()); - - var @int = _fixture.Create(); - thing.EmitIgnore(@int); - var events = thing.ThingContext.Events[nameof(LampThing.Int)].ToArray(); - events.Should().BeEmpty(); - } - - public class LampThing : Thing - { - public override string Name => nameof(LampThing); - - public event Action InvalidEvent; - - [ThingEvent(Ignore = true)] - public event EventHandler Ignore; - - public event EventHandler Int; - public event EventHandler DateTime; - public event EventHandler Decimal; - public event EventHandler Any; - - internal void EmitInvalid(int value) - => InvalidEvent?.Invoke(value); - - internal void EmitIgnore(int value) - => Ignore?.Invoke(this, value); - - internal void Emit(int value) - => Int?.Invoke(this, value); - - internal void Emit(decimal? value) - => Decimal?.Invoke(this, value); - - internal void Emit(DateTime value) - => DateTime?.Invoke(this, value); - - internal void Emit(object value) - => Any?.Invoke(this, value); - } - } -} diff --git a/test/Mozilla.IoT.WebThing.Test/Generator/PropertyInterceptFactoryTest.cs b/test/Mozilla.IoT.WebThing.Test/Generator/PropertyInterceptFactoryTest.cs deleted file mode 100644 index 1a0065a..0000000 --- a/test/Mozilla.IoT.WebThing.Test/Generator/PropertyInterceptFactoryTest.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System.Text.Json; -using AutoFixture; -using FluentAssertions; -using Mozilla.IoT.WebThing.Attributes; -using Mozilla.IoT.WebThing.Extensions; -using Mozilla.IoT.WebThing.Factories; -using Mozilla.IoT.WebThing.Factories.Generator.Properties; -using Xunit; - -namespace Mozilla.IoT.WebThing.Test.Generator -{ - public class PropertyInterceptFactoryTest - { - private readonly Fixture _fixture; - private readonly LampThing _thing; - private readonly PropertiesInterceptFactory _factory; - - public PropertyInterceptFactoryTest() - { - _fixture = new Fixture(); - _thing = new LampThing(); - _factory = new PropertiesInterceptFactory(_thing, new ThingOption - { - - }); - } - - [Fact] - public void Ignore() - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var properties = _factory.Create(); - properties.GetProperties().ContainsKey(nameof(LampThing.Ignore)).Should().BeFalse(); - } - - - [Fact] - public void GetValue() - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - var id = _fixture.Create(); - var value = _fixture.Create(); - _thing.Id = id; - _thing.Value = value; - - var properties = _factory.Create(); - var values = properties.GetProperties(); - - values.ContainsKey("id").Should().BeTrue(); - values.ContainsKey("test").Should().BeTrue(); - - values["id"].Should().Be(id); - values["test"].Should().Be(value); - - properties.GetProperties(nameof(LampThing.Id))[nameof(LampThing.Id)].Should().Be(id); - properties.GetProperties("test")["test"].Should().Be(value); - } - - [Fact] - public void SetPropertyThatNotExist() - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var properties = _factory.Create(); - var result = properties.SetProperty(_fixture.Create(), _fixture.Create()); - result.Should().Be(SetPropertyResult.NotFound); - } - - [Fact] - public void SetPropertyWithoutValidation() - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - var id = _fixture.Create(); - - var doc = JsonDocument.Parse($"{{ \"p\": {id} }}"); - var properties = _factory.Create(); - var result = properties.SetProperty(nameof(LampThing.Id), doc.RootElement.GetProperty("p")); - result.Should().Be(SetPropertyResult.Ok); - _thing.Id.Should().Be(id); - } - - [Theory] - [InlineData(-1)] - [InlineData(101)] - [InlineData(31)] - public void SetPropertyWithInvalidationValue(int value) - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var initial = _fixture.Create(); - _thing.Valid = initial; - var doc = JsonDocument.Parse($"{{ \"p\": {value} }}"); - var properties = _factory.Create(); - var result = properties.SetProperty(nameof(LampThing.Valid), doc.RootElement.GetProperty("p")); - result.Should().Be(SetPropertyResult.InvalidValue); - _thing.Valid.Should().Be(initial); - } - - [Theory] - [InlineData(2)] - [InlineData(100)] - public void SetPropertyWithValidationValue(int value) - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var initial = _fixture.Create(); - _thing.Valid = initial; - var doc = JsonDocument.Parse($"{{ \"p\": {value} }}"); - var properties = _factory.Create(); - var result = properties.SetProperty(nameof(LampThing.Valid), doc.RootElement.GetProperty("p")); - result.Should().Be(SetPropertyResult.Ok); - _thing.Valid.Should().Be(value); - } - - [Fact] - public void SetPropertyWithInvalidationRange() - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var value = _fixture.Create(); - var initial = _fixture.Create(); - _thing.Range = initial; - var doc = JsonDocument.Parse($"{{ \"p\": \"{value}\" }}"); - var properties = _factory.Create(); - var result = properties.SetProperty(nameof(LampThing.Range), doc.RootElement.GetProperty("p")); - result.Should().Be(SetPropertyResult.InvalidValue); - _thing.Range.Should().Be(initial); - } - - [Theory] - [InlineData("AAA")] - [InlineData("BBB")] - public void SetPropertyWithValidationRange(string value) - { - CodeGeneratorFactory.Generate(_thing, new []{ _factory }); - - var initial = _fixture.Create(); - _thing.Range = initial; - var doc = JsonDocument.Parse($"{{ \"p\": \"{value}\" }}"); - var properties = _factory.Create(); - var result = properties.SetProperty(nameof(LampThing.Range), doc.RootElement.GetProperty("p")); - result.Should().Be(SetPropertyResult.Ok); - _thing.Range.Should().Be(value); - } - - - public class LampThing : Thing - { - public override string Name => nameof(LampThing); - - public int Id { get; set; } - - [ThingProperty(Name = "test")] - public string Value { get; set; } - - [ThingProperty(Ignore = true)] - public bool Ignore { get; set; } - - [ThingProperty(Minimum = 0, Maximum = 100, MultipleOf = 2)] - public int Valid { get; set; } - - [ThingProperty(Enum = new object[]{ "AAA", "BBB"})] - public string Range { get; set; } - } - } -} diff --git a/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj b/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj index e60ed09..f0a634b 100644 --- a/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj +++ b/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj @@ -3,6 +3,7 @@ $(ThingAppTargetFrameworks) false + disable diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Boolean/PropertyBooleanTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Boolean/PropertyBooleanTest.cs new file mode 100644 index 0000000..ff1739a --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Boolean/PropertyBooleanTest.cs @@ -0,0 +1,112 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Boolean; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Boolean +{ + public class PropertyBooleanTest + { + private readonly BoolThing _thing; + private readonly Fixture _fixture; + + public PropertyBooleanTest() + { + _thing = new BoolThing(); + _fixture = new Fixture(); + } + + #region No Nullable + + private PropertyBoolean CreateNoNullable() + => new PropertyBoolean(_thing, + thing => ((BoolThing)thing).Bool, + (thing, value) => ((BoolThing)thing).Bool = (bool)value, + false); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetNoNullableWithValue(bool value) + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value.ToString().ToLower()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Bool.Should().Be(value); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + #region Nullable + + private PropertyBoolean CreateNullable() + => new PropertyBoolean(_thing, + thing => ((BoolThing)thing).NullableBool, + (thing, value) => ((BoolThing)thing).NullableBool = (bool?)value, + true); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetNullableWithValue(bool value) + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value.ToString().ToLower()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableBool.Should().NotBeNull(); + _thing.NullableBool.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableBool.Should().BeNull(); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create().ToString()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class BoolThing : Thing + { + public override string Name => "bool-thing"; + + public bool Bool { get; set; } + public bool? NullableBool { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyByteTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyByteTest.cs new file mode 100644 index 0000000..b280395 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyByteTest.cs @@ -0,0 +1,271 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyByteTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyByteTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyByte CreateNoNullable(byte[]? enums = null, byte? min = null, byte? max = null, byte? multipleOf = null) + => new PropertyByte(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (byte)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(byte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(byte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(byte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(byte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyByte CreateNullable(byte[]? enums = null, byte? min = null, byte? max = null, byte? multipleOf = null) + => new PropertyByte(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (byte?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(byte value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(byte value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int param) + { + var value = Convert.ToByte(param); + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int param) + { + var value = Convert.ToByte(param); + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public byte Number { get; set; } + public byte? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDecimalTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDecimalTest.cs new file mode 100644 index 0000000..60ed8f1 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDecimalTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyDecimalTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyDecimalTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyDecimal CreateNoNullable(decimal[]? enums = null, decimal? min = null, decimal? max = null, decimal? multipleOf = null) + => new PropertyDecimal(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (decimal)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(decimal value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(decimal value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(decimal value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(decimal value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyDecimal CreateNullable(decimal[]? enums = null, decimal? min = null, decimal? max = null, decimal? multipleOf = null) + => new PropertyDecimal(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (decimal?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(decimal value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(decimal value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(decimal value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(decimal value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public decimal Number { get; set; } + public decimal? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDoubleTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDoubleTest.cs new file mode 100644 index 0000000..b62a361 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyDoubleTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyDoubleTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyDoubleTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyDouble CreateNoNullable(double[]? enums = null, double? min = null, double? max = null, double? multipleOf = null) + => new PropertyDouble(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (double)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(double value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(double value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(double value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(double value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyDouble CreateNullable(double[]? enums = null, double? min = null, double? max = null, double? multipleOf = null) + => new PropertyDouble(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (double?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(double value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(double value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(double value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(double value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public double Number { get; set; } + public double? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyFloatTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyFloatTest.cs new file mode 100644 index 0000000..6461026 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyFloatTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyFloatTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyFloatTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyFloat CreateNoNullable(float[]? enums = null, float? min = null, float? max = null, float? multipleOf = null) + => new PropertyFloat(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (float)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(float value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(float value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(float value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(float value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyFloat CreateNullable(float[]? enums = null, float? min = null, float? max = null, float? multipleOf = null) + => new PropertyFloat(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (float?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(float value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(float value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(float value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(float value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public float Number { get; set; } + public float? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyIntTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyIntTest.cs new file mode 100644 index 0000000..feea253 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyIntTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyIntTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyIntTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyInt CreateNoNullable(int[]? enums = null, int? min = null, int? max = null, int? multipleOf = null) + => new PropertyInt(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (int)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(int value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(int value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(int value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(int value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyInt CreateNullable(int[]? enums = null, int? min = null, int? max = null, int? multipleOf = null) + => new PropertyInt(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (int?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(int value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(int value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(int value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(int value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public int Number { get; set; } + public int? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyLongTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyLongTest.cs new file mode 100644 index 0000000..d463371 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyLongTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyLongTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyLongTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyLong CreateNoNullable(long[]? enums = null, long? min = null, long? max = null, long? multipleOf = null) + => new PropertyLong(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (long)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(long value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(long value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(long value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(long value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyLong CreateNullable(long[]? enums = null, long? min = null, long? max = null, long? multipleOf = null) + => new PropertyLong(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (long?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(long value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(long value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(long value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(long value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public long Number { get; set; } + public long? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertySByteTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertySByteTest.cs new file mode 100644 index 0000000..195a057 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertySByteTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertySByteTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertySByteTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertySByte CreateNoNullable(sbyte[]? enums = null, sbyte? min = null, sbyte? max = null, sbyte? multipleOf = null) + => new PropertySByte(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (sbyte)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(sbyte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(sbyte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(sbyte value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(sbyte value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertySByte CreateNullable(sbyte[]? enums = null, sbyte? min = null, sbyte? max = null, sbyte? multipleOf = null) + => new PropertySByte(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (sbyte?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(sbyte value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(sbyte value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(sbyte value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(sbyte value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public sbyte Number { get; set; } + public sbyte? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyShortTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyShortTest.cs new file mode 100644 index 0000000..d01d5d0 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyShortTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyShortTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyShortTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyShort CreateNoNullable(short[]? enums = null, short? min = null, short? max = null, short? multipleOf = null) + => new PropertyShort(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (short)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(short value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(short value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(short value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(short value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyShort CreateNullable(short[]? enums = null, short? min = null, short? max = null, short? multipleOf = null) + => new PropertyShort(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (short?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(short value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(short value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(short value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(short value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public short Number { get; set; } + public short? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUIntTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUIntTest.cs new file mode 100644 index 0000000..b14e496 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUIntTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyUIntTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyUIntTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyUInt CreateNoNullable(uint[]? enums = null, uint? min = null, uint? max = null, uint? multipleOf = null) + => new PropertyUInt(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (uint)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(uint value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(uint value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(uint value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(uint value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyUInt CreateNullable(uint[]? enums = null, uint? min = null, uint? max = null, uint? multipleOf = null) + => new PropertyUInt(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (uint?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(uint value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(uint value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(uint value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(uint value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public uint Number { get; set; } + public uint? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyULongTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyULongTest.cs new file mode 100644 index 0000000..1ec63d0 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyULongTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyULongTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyULongTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyULong CreateNoNullable(ulong[]? enums = null, ulong? min = null, ulong? max = null, ulong? multipleOf = null) + => new PropertyULong(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (ulong)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(ulong value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(ulong value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(ulong value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(ulong value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyULong CreateNullable(ulong[]? enums = null, ulong? min = null, ulong? max = null, ulong? multipleOf = null) + => new PropertyULong(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (ulong?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(ulong value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(ulong value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(ulong value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(ulong value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public ulong Number { get; set; } + public ulong? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUShortTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUShortTest.cs new file mode 100644 index 0000000..a3f1b12 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Numbers/PropertyUShortTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.Number; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Numbers +{ + public class PropertyUShortTest + { + private readonly NumberThing _thing; + private readonly Fixture _fixture; + + public PropertyUShortTest() + { + _fixture = new Fixture(); + _thing = new NumberThing(); + } + + #region No Nullable + private PropertyUShort CreateNoNullable(ushort[]? enums = null, ushort? min = null, ushort? max = null, ushort? multipleOf = null) + => new PropertyUShort(_thing, + thing => ((NumberThing)thing).Number, + (thing, value) => ((NumberThing)thing).Number = (ushort)value, + false, min, max, multipleOf, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNoNullableWithMinValue(ushort value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNoNullableWithMaxValue(ushort value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Number.Should().Be(10); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNoNullableWithMinValue(ushort value) + { + var property = CreateNoNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNoNullableWithMaxValue(ushort value) + { + var property = CreateNoNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMultipleOfValue() + { + var property = CreateNoNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyUShort CreateNullable(ushort[]? enums = null, ushort? min = null, ushort? max = null, ushort? multipleOf = null) + => new PropertyUShort(_thing, + thing => ((NumberThing)thing).NullableNumber, + (thing, value) => ((NumberThing)thing).NullableNumber = (ushort?)value, + true, min, max, multipleOf, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().NotBeNull(); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + } + + [Theory] + [InlineData(11)] + [InlineData(10)] + public void SetNullableWithMinValue(ushort value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Theory] + [InlineData(9)] + [InlineData(10)] + public void SetNullableWithMaxValue(ushort value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(value); + } + + [Fact] + public void SetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 10 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableNumber.Should().Be(10); + } + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(bool) ? _fixture.Create().ToString().ToLower() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {_fixture.Create()} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(8)] + [InlineData(9)] + public void TrySetNullableWithMinValue(ushort value) + { + var property = CreateNullable(min: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(12)] + [InlineData(11)] + public void TrySetNullableWithMaxValue(ushort value) + { + var property = CreateNullable(max: 10); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWithMultipleOfValue() + { + var property = CreateNullable(multipleOf: 2); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": 9 }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + public class NumberThing : Thing + { + public override string Name => "number-thing"; + + public ushort Number { get; set; } + public ushort? NullableNumber { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/PropertyReadOnlyTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/PropertyReadOnlyTest.cs new file mode 100644 index 0000000..5120969 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/PropertyReadOnlyTest.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties +{ + public class PropertyReadOnlyTest + { + private readonly Fixture _fixture; + private readonly ReadOnlyThing _thing; + + public PropertyReadOnlyTest() + { + _fixture = new Fixture(); + _thing = new ReadOnlyThing(); + } + + [Fact] + public void GetValue() + { + var property = new PropertyReadOnly(_thing, thing => ((ReadOnlyThing)thing).Reader); + + _thing.Reader = _fixture.Create(); + + property.GetValue().Should().NotBeNull(); + property.GetValue().Should().Be(_thing.Reader); + } + + [Fact] + public void TrySet() + { + var property = new PropertyReadOnly(_thing, thing => ((ReadOnlyThing)thing).Reader); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement).Should().Be(SetPropertyResult.ReadOnly); + property.GetValue().Should().BeNull(); + } + + public class ReadOnlyThing : Thing + { + public override string Name => "read-only-thing"; + + + public string Reader { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyCharTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyCharTest.cs new file mode 100644 index 0000000..2a0fd52 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyCharTest.cs @@ -0,0 +1,187 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyCharTest + { + private readonly CharThing _thing; + private readonly Fixture _fixture; + + public PropertyCharTest() + { + _fixture = new Fixture(); + _thing = new CharThing(); + } + + #region No Nullable + private PropertyChar CreateNoNullable(char[] enums = null) + => new PropertyChar(_thing, + thing => ((CharThing)thing).Char, + (thing, value) => ((CharThing)thing).Char = (char)value!, + false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize(new Serializer{Input = value}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Char.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize(new Serializer{Input = value}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Char.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize( + new Serializer{Input = _fixture.Create()}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyChar CreateNullable(char[] enums = null) + => new PropertyChar(_thing, + thing => ((CharThing)thing).NullableChar, + (thing, value) => ((CharThing)thing).NullableChar = (char?)value, + true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize( + new Serializer{Input = value}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableChar.Should().NotBeNull(); + _thing.NullableChar.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableChar.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize( + new Serializer{Input = value}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableChar.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize( + new Serializer{Input = _fixture.Create()}, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class CharThing : Thing + { + public override string Name => "char-thing"; + + public char Char { get; set; } + public char? NullableChar { get; set; } + } + + public class Serializer + { + public char Input { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeOffsetTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeOffsetTest.cs new file mode 100644 index 0000000..8b0c7e8 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeOffsetTest.cs @@ -0,0 +1,152 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyDateTimeOffsetTest + { + private readonly DateTimeOffsetThing _thing; + private readonly Fixture _fixture; + + public PropertyDateTimeOffsetTest() + { + _fixture = new Fixture(); + _thing = new DateTimeOffsetThing(); + } + + #region No Nullable + private PropertyDateTimeOffset CreateNoNullable(DateTimeOffset[] enums = null) + => new PropertyDateTimeOffset(_thing, + thing => ((DateTimeOffsetThing)thing).DateTimeOffset, + (thing, value) => ((DateTimeOffsetThing)thing).DateTimeOffset = (DateTimeOffset)value!, + false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.DateTimeOffset.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.DateTimeOffset.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value:O} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyDateTimeOffset CreateNullable(DateTimeOffset[] enums = null) + => new PropertyDateTimeOffset(_thing, + thing => ((DateTimeOffsetThing)thing).NullableDateTimeOffset, + (thing, value) => ((DateTimeOffsetThing)thing).NullableDateTimeOffset = (DateTimeOffset?)value, + true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTimeOffset.Should().NotBeNull(); + _thing.NullableDateTimeOffset.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTimeOffset.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTimeOffset.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class DateTimeOffsetThing : Thing + { + public override string Name => "datetimeoffset-thing"; + + public DateTimeOffset DateTimeOffset { get; set; } + public DateTimeOffset? NullableDateTimeOffset { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeTest.cs new file mode 100644 index 0000000..b1bc0c6 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyDateTimeTest.cs @@ -0,0 +1,152 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyDateTimeTest + { + private readonly DateTimeThing _thing; + private readonly Fixture _fixture; + + public PropertyDateTimeTest() + { + _fixture = new Fixture(); + _thing = new DateTimeThing(); + } + + #region No Nullable + private PropertyDateTime CreateNoNullable(DateTime[]? enums = null) + => new PropertyDateTime(_thing, + thing => ((DateTimeThing)thing).DateTime, + (thing, value) => ((DateTimeThing)thing).DateTime = (DateTime)value, + false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.DateTime.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.DateTime.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyDateTime CreateNullable(DateTime[] enums = null) + => new PropertyDateTime(_thing, + thing => ((DateTimeThing)thing).NullableDateTime, + (thing, value) => ((DateTimeThing)thing).NullableDateTime = (DateTime?)value, + true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTime.Should().NotBeNull(); + _thing.NullableDateTime.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTime.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value:O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableDateTime.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create():O}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class DateTimeThing : Thing + { + public override string Name => "datetime-thing"; + + public DateTime DateTime { get; set; } + public DateTime? NullableDateTime { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyEnumTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyEnumTest.cs new file mode 100644 index 0000000..f27fa6e --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyEnumTest.cs @@ -0,0 +1,156 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyEnumTest + { + private readonly EnumThing _thing; + private readonly Fixture _fixture; + + public PropertyEnumTest() + { + _fixture = new Fixture(); + _thing = new EnumThing(); + } + + #region No Nullable + private PropertyEnum CreateNoNullable() + => new PropertyEnum(_thing, + thing => ((EnumThing)thing).Enum, + (thing, value) => ((EnumThing)thing).Enum = (Foo)value!, + false, typeof(Foo)); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Enum.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var property = CreateNoNullable(); + foreach (var value in typeof(Foo).GetEnumValues()) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Enum.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyEnum CreateNullable() + => new PropertyEnum(_thing, + thing => ((EnumThing)thing).NullableEnum, + (thing, value) => ((EnumThing)thing).NullableEnum = (Foo?)value, + true, typeof(Foo)); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableEnum.Should().NotBeNull(); + _thing.NullableEnum.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableEnum.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var property = CreateNullable(); + foreach (var value in typeof(Foo).GetEnumValues()) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableEnum.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class EnumThing : Thing + { + public override string Name => "enum-thing"; + + public Foo Enum { get; set; } + public Foo? NullableEnum { get; set; } + } + + public enum Foo + { + A, + B, + C, + D + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyGuidTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyGuidTest.cs new file mode 100644 index 0000000..7eb2090 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyGuidTest.cs @@ -0,0 +1,152 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyGuidTest + { + private readonly GuidThing _thing; + private readonly Fixture _fixture; + + public PropertyGuidTest() + { + _fixture = new Fixture(); + _thing = new GuidThing(); + } + + #region No Nullable + private PropertyGuid CreateNoNullable(Guid[]? enums = null) + => new PropertyGuid(_thing, + thing => ((GuidThing)thing).Guid, + (thing, value) => ((GuidThing)thing).Guid = (Guid)value, + false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Guid.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.Guid.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyGuid CreateNullable(Guid[] enums = null) + => new PropertyGuid(_thing, + thing => ((GuidThing)thing).NullableGuid, + (thing, value) => ((GuidThing)thing).NullableGuid = (Guid?)value, + true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableGuid.Should().NotBeNull(); + _thing.NullableGuid.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableGuid.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableGuid.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DateTime))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class GuidThing : Thing + { + public override string Name => "guid-thing"; + + public Guid Guid { get; set; } + public Guid? NullableGuid { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyStringTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyStringTest.cs new file mode 100644 index 0000000..491eb5b --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyStringTest.cs @@ -0,0 +1,180 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyStringTest + { + private readonly StringThing _thing; + private readonly Fixture _fixture; + + public PropertyStringTest() + { + _fixture = new Fixture(); + _thing = new StringThing(); + } + + private PropertyString CreateProperty(string[]? enums = null, string pattern = null, int? minimum = null, int? maximum = null, bool isNullable = false) + => new PropertyString(_thing, + thing => ((StringThing)thing).String, + (thing, value) => ((StringThing)thing).String = (string)value, + isNullable, + minimum, maximum, pattern, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + } + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateProperty(isNullable: true); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().NotBeNull(); + _thing.String.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateProperty(isNullable: true); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().BeNull(); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateProperty(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + } + } + + [Fact] + public void SetNoNullableWithValuePattern() + { + var property = CreateProperty(pattern: @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$"); + const string value = "test@test.com"; + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMinLength() + { + var property = CreateProperty(minimum: 1); + var value = _fixture.Create().ToString(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + + value = _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithMaxLength() + { + var property = CreateProperty(maximum: 37); + var value = _fixture.Create(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + + value = _fixture.Create() + _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.String.Should().Be(value); + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(bool))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"{_fixture.Create().ToString().ToLower()}"; + var property = CreateProperty(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateProperty(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMinLength() + { + var property = CreateProperty(minimum: 1); + var value = string.Empty; + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + + value = _fixture.Create(); + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": null }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithMaxLength() + { + var property = CreateProperty(maximum: 36); + var value = _fixture.Create() + _fixture.Create().ToString()[0]; + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + + jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": null }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithValuePattern() + { + var property = CreateProperty(pattern: @"^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$"); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + public class StringThing : Thing + { + public override string Name => "string-thing"; + + public string String { get; set; } + } + } +} diff --git a/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyTimeSpanTest.cs b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyTimeSpanTest.cs new file mode 100644 index 0000000..c243a52 --- /dev/null +++ b/test/Mozilla.IoT.WebThing.Test/Properties/Strings/PropertyTimeSpanTest.cs @@ -0,0 +1,152 @@ +using System; +using System.Text.Json; +using AutoFixture; +using FluentAssertions; +using Mozilla.IoT.WebThing.Properties; +using Mozilla.IoT.WebThing.Properties.String; +using Xunit; + +namespace Mozilla.IoT.WebThing.Test.Properties.Strings +{ + public class PropertyTimeSpanTest + { + private readonly TimeSpanThing _thing; + private readonly Fixture _fixture; + + public PropertyTimeSpanTest() + { + _fixture = new Fixture(); + _thing = new TimeSpanThing(); + } + + #region No Nullable + private PropertyTimeSpan CreateNoNullable(TimeSpan[]? enums = null) + => new PropertyTimeSpan(_thing, + thing => ((TimeSpanThing)thing).TimeSpan, + (thing, value) => ((TimeSpanThing)thing).TimeSpan = (TimeSpan)value, + false, enums); + + [Fact] + public void SetNoNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.TimeSpan.Should().Be(value); + } + + [Fact] + public void SetNoNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.TimeSpan.Should().Be(value); + } + } + + [Fact] + public void TrySetNoNullableWithNullValue() + { + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNoNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNoNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNoNullableWithEnumValue() + { + var values = _fixture.Create(); + var property = CreateNoNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + #endregion + + #region Nullable + + private PropertyTimeSpan CreateNullable(TimeSpan[] enums = null) + => new PropertyTimeSpan(_thing, + thing => ((TimeSpanThing)thing).NullableTimeSpan, + (thing, value) => ((TimeSpanThing)thing).NullableTimeSpan = (TimeSpan?)value, + true, enums); + + [Fact] + public void SetNullableWithValue() + { + var value = _fixture.Create(); + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableTimeSpan.Should().NotBeNull(); + _thing.NullableTimeSpan.Should().Be(value); + } + + [Fact] + public void SetNullableWithNullValue() + { + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize(@"{ ""input"": null }"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableTimeSpan.Should().BeNull(); + } + + [Fact] + public void SetNullableWithValueEnums() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + foreach (var value in values) + { + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{value}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.Ok); + _thing.NullableTimeSpan.Should().Be(value); + } + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + public void TrySetNullableWitInvalidValue(Type type) + { + var value = type == typeof(int) ? _fixture.Create().ToString() : $@"""{_fixture.Create()}"""; + var property = CreateNullable(); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": {value} }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + [Fact] + public void TrySetNullableWitInvalidValueAndNotHaveValueInEnum() + { + var values = _fixture.Create(); + var property = CreateNullable(values); + var jsonElement = JsonSerializer.Deserialize($@"{{ ""input"": ""{_fixture.Create()}"" }}"); + property.SetValue(jsonElement.GetProperty("input")).Should().Be(SetPropertyResult.InvalidValue); + } + + #endregion + + public class TimeSpanThing : Thing + { + public override string Name => "time-span-thing"; + + public TimeSpan TimeSpan { get; set; } + public TimeSpan? NullableTimeSpan { get; set; } + } + } +}