Fix for circular depedency [hold merge till other repos are ready] (#190)

* deviceProperties

* Json format

* comments

* Fix for Review comments

* GetPropertyNames

* GetPropertyNames

* nits

* Merge with master

** Resolve conflict
** Add tests

* Sort using

* Fix typo in file name

* Update DevicePropertyNames API

Change response from hashset to list

* Add API specs doc

* Fix indentation

* Fix typo

* Add test

* Add Comments

* deviceModelProperties

* api specs for device model Properties

* api spec update in sln

* Removing Comments

* DeviceModelPropertiesController Json change

* caps

* update API Spec

* reverting env var

* deviceModelProperties

* PREFIX moved to service model

* review comments some exception

* Revert "review comments some exception"

This reverts commit 6220a93d44.

* adding comments

* Update SomeException.cs

* Update SomeException.cs

* fix typo

* nit
This commit is contained in:
Isaac 2018-06-20 15:56:58 -07:00 коммит произвёл Parvez
Родитель 7f4befe616
Коммит 341f3020e9
9 изменённых файлов: 330 добавлений и 36 удалений

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
@ -9,6 +9,7 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
using Moq;
using Newtonsoft.Json.Linq;
using Services.Test.helpers;
using Xunit;
@ -100,6 +101,31 @@ namespace Services.Test
Assert.Equal(DEVICE_MODEL_ID, result.Id);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItReturnsPropertyNamesOfDeviceModels()
{
// Arrange
var properties = new Dictionary<string, object>();
properties.Add("Type", "chiller");
properties.Add("Firmware", "1.0");
properties.Add("Location", "Building 2");
properties.Add("Model", "CH101");
var deviceModels = this.GetDeviceModelsWithProperties(properties);
this.customDeviceModels
.Setup(x => x.GetListAsync())
.ReturnsAsync(deviceModels);
// Act
var result = this.target.GetPropertyNamesAsync().Result;
// Assert
Assert.Equal(properties.Count, result.Count);
foreach (var prop in result)
{
Assert.True(properties.ContainsKey(prop.Split('.')[2]));
}
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsResourceNotFoundExceptionWhenDeviceModelNotFound()
{
@ -128,6 +154,13 @@ namespace Services.Test
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsExceptionWhenDeleteDeviceModelFailed()
{
/* SomeException is required to verify that the exception thrown is the one
* configured in Arrange expression, and the test doesn't pass for the wrong
* reason. That's why we use a helper class SomeException which
* doesn't exist in the application. Configuring GetPropertyNameAsync to
* throw SomeException allows us to verify that the exception thrown is
* exactly SomeException and not something else. */
// Arrange
this.customDeviceModels
.Setup(x => x.DeleteAsync(It.IsAny<string>()))
@ -255,5 +288,22 @@ namespace Services.Test
.Setup(x => x.GetListAsync())
.ReturnsAsync(deviceModelsList);
}
private List<DeviceModel> GetDeviceModelsWithProperties(Dictionary<string, object> properties)
{
var deviceModels = new List<DeviceModel>
{
new DeviceModel {
Id = "Id_1",
Properties = properties
},
new DeviceModel {
Id = "Id_2",
Properties = properties
}
};
return deviceModels;
}
}
}

Просмотреть файл

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Newtonsoft.Json.Linq;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
@ -36,6 +37,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// Delete a custom device model.
/// </summary>
Task DeleteAsync(string id);
/// <summary>
/// Get property names from all device models.
/// </summary>
Task<List<string>> GetPropertyNamesAsync();
}
/// <summary>
@ -51,6 +57,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly ILogger log;
private readonly ICustomDeviceModels customDeviceModels;
private readonly IStockDeviceModels stockDeviceModels;
private const string REPORTED_PREFIX = "Properties.Reported.";
public DeviceModels(
ICustomDeviceModels customDeviceModels,
@ -143,6 +150,33 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
await this.customDeviceModels.DeleteAsync(id);
}
/// <summary>
/// Get property names from all device models.
/// </summary>
public async Task<List<string>> GetPropertyNamesAsync()
{
var list = await this.GetListAsync();
var properties = new HashSet<string>();
foreach (var model in list)
{
if (model.Properties != null)
{
foreach (var property in model.Properties)
{
this.PreparePropertyNames(properties, property.Value, property.Key);
}
}
}
List<string> result = new List<string>();
foreach (string property in properties)
{
result.Add(REPORTED_PREFIX + property);
}
return result;
}
/// <summary>
/// Returns True if there is a stock model with the given Id
/// </summary>
@ -153,5 +187,36 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
return this.stockDeviceModels.GetList()
.Any(model => id.Equals(model.Id, StringComparison.InvariantCultureIgnoreCase));
}
/// <summary>
/// Transforms property names from Json Object to "xxx.yyy.zzz" format
/// </summary>
private void PreparePropertyNames(HashSet<string> set, object obj, string prefix)
{
/* Sample conversion:
* from -> Foo : {
* Bar : Properties
* }
*
* to -> Foo.Bar.Properties
*/
if (obj is JValue)
{
set.Add(prefix);
return;
}
if (obj is bool || obj is string || double.TryParse(obj.ToString(), out _))
{
set.Add(prefix);
return;
}
foreach (var item in (obj as JToken).Values())
{
var path = item.Path;
this.PreparePropertyNames(set, item, $"{prefix}.{(path.Contains(".") ? path.Substring(path.LastIndexOf('.') + 1) : path)}");
}
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
namespace Services.Test.helpers
{
///<summary>
/// This class is used to inject exceptions in the code under test
/// and to verify that the system fails with the injected exception,
/// i.e. not any exception, to be sure unit tests wouldn't pass in case
/// a different exception is occurring, i.e. to avoid false positives.
///</summary>
public class SomeException : Exception
{
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers;
using Moq;
using Services.Test.helpers;
using WebService.Test.helpers;
using Xunit;
namespace WebService.Test.v1.Controllers
{
public class DeviceModelPropertiesControllerTest
{
private readonly Mock<IDeviceModels> deviceModelsService;
private readonly DeviceModelPropertiesController target;
public DeviceModelPropertiesControllerTest()
{
this.deviceModelsService = new Mock<IDeviceModels>();
this.target = new DeviceModelPropertiesController(this.deviceModelsService.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItReturnsTheListOfPropertyNames()
{
// Arrange
var properties = new List<string>
{
"Type", "Firmware" , "Location" ,"Model", "Latitude" ,"Longitude"
};
this.deviceModelsService
.Setup(x => x.GetPropertyNamesAsync())
.ReturnsAsync(properties);
// Act
var result = this.target.GetAsync().Result;
// Assert
Assert.Equal(properties.Count, result.Items.Count);
foreach (var resultItem in result.Items)
{
Assert.Contains(resultItem, properties);
}
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsExceptionWhenGetPropertyNamesFailed()
{
// Arrange
this.deviceModelsService
.Setup(x => x.GetPropertyNamesAsync())
.ThrowsAsync(new SomeException());
// Act & Assert
Assert.ThrowsAsync<SomeException>(
async () => await this.target.GetAsync())
.Wait(Constants.TEST_TIMEOUT);
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers
{
[Route(Version.PATH + "/[controller]"), TypeFilter(typeof(ExceptionsFilterAttribute))]
public class DeviceModelPropertiesController : Controller
{
private readonly IDeviceModels deviceModelsService;
public DeviceModelPropertiesController(IDeviceModels deviceModelsService)
{
this.deviceModelsService = deviceModelsService;
}
[HttpGet]
public async Task<DeviceModelPropertyListApiModel> GetAsync()
{
return new DeviceModelPropertyListApiModel(await this.deviceModelsService.GetPropertyNamesAsync());
}
}
}

Просмотреть файл

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models;

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
{
public class DeviceModelPropertyListApiModel
{
[JsonProperty(PropertyName = "Items")]
public List<string> Items { get; set; }
[JsonProperty(PropertyName = "$metadata")]
public Dictionary<string, string> Metadata => new Dictionary<string, string>
{
{ "$type", "DeviceModelPropertyList;" + Version.NUMBER },
{ "$uri", "/" + Version.PATH + "/deviceModelProperties" }
};
public DeviceModelPropertyListApiModel()
{
this.Items = new List<string>();
}
/// <summary>Map a service model to the corresponding API model</summary>
public DeviceModelPropertyListApiModel(List<string> values)
{
this.Items = values;
}
}
}

Просмотреть файл

@ -1,17 +1,19 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebService.Test", "WebService.Test\WebService.Test.csproj", "{9DFE22E7-03DD-4F30-BCC1-33F69BA15192}"
# Visual Studio 15
VisualStudioVersion = 15.0.27428.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebService.Test", "WebService.Test\WebService.Test.csproj", "{9DFE22E7-03DD-4F30-BCC1-33F69BA15192}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{FBAB42CE-0D43-476A-B231-4C0105A7C67C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "Services\Services.csproj", "{FBAB42CE-0D43-476A-B231-4C0105A7C67C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Test", "Services.Test\Services.Test.csproj", "{A135F921-CC62-40CD-A3C6-72DD95BB7E72}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services.Test", "Services.Test\Services.Test.csproj", "{A135F921-CC62-40CD-A3C6-72DD95BB7E72}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimulationAgent", "SimulationAgent\SimulationAgent.csproj", "{93EBB353-A0B6-440F-BD34-17240C4F421B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimulationAgent", "SimulationAgent\SimulationAgent.csproj", "{93EBB353-A0B6-440F-BD34-17240C4F421B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebService", "WebService\WebService.csproj", "{21FEAC28-BB2D-4827-B62A-0476BF44D602}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebService", "WebService\WebService.csproj", "{21FEAC28-BB2D-4827-B62A-0476BF44D602}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimulationAgent.Test", "SimulationAgent.Test\SimulationAgent.Test.csproj", "{DFE737E7-9FAA-4B08-A2B5-37F2E858FFAF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimulationAgent.Test", "SimulationAgent.Test\SimulationAgent.Test.csproj", "{DFE737E7-9FAA-4B08-A2B5-37F2E858FFAF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{71787873-D1A0-4D9E-BC84-942E0FB3841E}"
ProjectSection(SolutionItems) = preProject
@ -44,15 +46,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{2B58
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054}"
ProjectSection(SolutionItems) = preProject
scripts\docker\.dockerignore = scripts\docker\.dockerignore
scripts\docker\build = scripts\docker\build
scripts\docker\build.cmd = scripts\docker\build.cmd
scripts\docker\Dockerfile = scripts\docker\Dockerfile
scripts\docker\run.cmd = scripts\docker\run.cmd
scripts\docker\.dockerignore = scripts\docker\.dockerignore
scripts\docker\run = scripts\docker\run
scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml
scripts\docker\Dockerfile = scripts\docker\Dockerfile
scripts\docker\publish = scripts\docker\publish
scripts\docker\publish.cmd = scripts\docker\publish.cmd
scripts\docker\run = scripts\docker\run
scripts\docker\run.cmd = scripts\docker\run.cmd
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{4C88469C-C551-435D-AE2C-36C58EE686A5}"
@ -72,20 +74,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{39EA
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0680130C-7156-4118-BE65-4734BA92638B}"
ProjectSection(SolutionItems) = preProject
docs\CODEOWNERS = docs\CODEOWNERS
docs\ISSUE_TEMPLATE.md = docs\ISSUE_TEMPLATE.md
docs\PULL_REQUEST_TEMPLATE.md = docs\PULL_REQUEST_TEMPLATE.md
docs\CODE_OF_CONDUCT.md = docs\CODE_OF_CONDUCT.md
docs\CONTRIBUTING.md = docs\CONTRIBUTING.md
docs\API_SPECS_DEVICE_MODELS.md = docs\API_SPECS_DEVICE_MODELS.md
docs\API_SPECS_SERVICE.md = docs\API_SPECS_SERVICE.md
docs\API_SPECS_SIMULATIONS.md = docs\API_SPECS_SIMULATIONS.md
docs\DEVICE_MODELS.md = docs\DEVICE_MODELS.md
docs\INDEX.md = docs\INDEX.md
docs\BREAKING_CHANGES.md = docs\BREAKING_CHANGES.md
docs\ENVIRONMENT_VARIABLES.md = docs\ENVIRONMENT_VARIABLES.md
EndProjectSection
ProjectSection(SolutionItems) = preProject
docs\API_SPECS_DEVICE_MODELS.md = docs\API_SPECS_DEVICE_MODELS.md
docs\API_SPECS_DEVICE_MODEL_PROPERTIES.md = docs\API_SPECS_DEVICE_MODEL_PROPERTIES.md
docs\API_SPECS_SERVICE.md = docs\API_SPECS_SERVICE.md
docs\API_SPECS_SIMULATIONS.md = docs\API_SPECS_SIMULATIONS.md
docs\BREAKING_CHANGES.md = docs\BREAKING_CHANGES.md
docs\CODE_OF_CONDUCT.md = docs\CODE_OF_CONDUCT.md
docs\CODEOWNERS = docs\CODEOWNERS
docs\CONTRIBUTING.md = docs\CONTRIBUTING.md
docs\DEVICE_MODELS.md = docs\DEVICE_MODELS.md
docs\ENVIRONMENT_VARIABLES.md = docs\ENVIRONMENT_VARIABLES.md
docs\INDEX.md = docs\INDEX.md
docs\ISSUE_TEMPLATE.md = docs\ISSUE_TEMPLATE.md
docs\PULL_REQUEST_TEMPLATE.md = docs\PULL_REQUEST_TEMPLATE.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -107,14 +110,14 @@ Global
{A135F921-CC62-40CD-A3C6-72DD95BB7E72}.Release|Any CPU.Build.0 = Release|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Release|Any CPU.Build.0 = Release|Any CPU
{93EBB353-A0B6-440F-BD34-17240C4F421B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Release|Any CPU.Build.0 = Release|Any CPU
{21FEAC28-BB2D-4827-B62A-0476BF44D602}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{DFE737E7-9FAA-4B08-A2B5-37F2E858FFAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFE737E7-9FAA-4B08-A2B5-37F2E858FFAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFE737E7-9FAA-4B08-A2B5-37F2E858FFAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -123,6 +126,16 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2B58D756-04D5-427F-9161-E1B45D35682A} = {71787873-D1A0-4D9E-BC84-942E0FB3841E}
{DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054} = {2B58D756-04D5-427F-9161-E1B45D35682A}
{4C88469C-C551-435D-AE2C-36C58EE686A5} = {2B58D756-04D5-427F-9161-E1B45D35682A}
{39EA50D7-B4CD-41C4-85EA-E53E89E9BC98} = {DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054}
{0680130C-7156-4118-BE65-4734BA92638B} = {71787873-D1A0-4D9E-BC84-942E0FB3841E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25EBA0C1-8A8C-4116-948B-626F1638B7DA}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.DotNetNamingPolicy = $1
@ -133,11 +146,4 @@ Global
$3.scope = text/x-csharp
$0.VersionControlPolicy = $4
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2B58D756-04D5-427F-9161-E1B45D35682A} = {71787873-D1A0-4D9E-BC84-942E0FB3841E}
{DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054} = {2B58D756-04D5-427F-9161-E1B45D35682A}
{4C88469C-C551-435D-AE2C-36C58EE686A5} = {2B58D756-04D5-427F-9161-E1B45D35682A}
{39EA50D7-B4CD-41C4-85EA-E53E89E9BC98} = {DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054}
{0680130C-7156-4118-BE65-4734BA92638B} = {71787873-D1A0-4D9E-BC84-942E0FB3841E}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,34 @@
API specifications - Device Model Properties
======================================
## Get a list of device model properties
The list of device model properties contains properties from all device models.
Request:
```
GET /v1/deviceModelProperties
```
Response:
```
200 OK
Content-Type: application/json
```
```json
{
"Items": [
"Properties.Reported.Type",
"Properties.Reported.Firmware",
"Properties.Reported.Model",
"Properties.Reported.Location",
"Properties.Reported.Latitude",
"Properties.Reported.Longitude",
"Properties.Reported.FirmwareUpdateStatus"
],
"$metadata": {
"$type": "DeviceModelPropertyList;1",
"$uri": "/v1/deviceModelProperties"
}
}
```