Skip to content

Commit

Permalink
Add lorawan device model custom propertis support (#417)
Browse files Browse the repository at this point in the history
  • Loading branch information
kbeaugrand authored Mar 9, 2022
1 parent 8c62f6d commit 1200105
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 109 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AzureIoTHub.Portal.Server.Tests.Unit.Controllers.V10
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Azure;
using Azure.Data.Tables;
using AzureIoTHub.Portal.Server.Controllers.V10;
using AzureIoTHub.Portal.Server.Entities;
using AzureIoTHub.Portal.Server.Factories;
using AzureIoTHub.Portal.Shared.Models.V10;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;

[TestFixture]
public class LoRaWANDeviceModelPropertiesControllerTests
{
private MockRepository mockRepository;

private Mock<ILogger<LoRaWANDeviceModelPropertiesController>> mockLogger;
private Mock<IMapper> mockMapper;
private Mock<ITableClientFactory> mockTableClientFactory;
private Mock<TableClient> mockDeviceTemplatesTableClient;
private Mock<TableClient> mockDeviceModelPropertiesTableClient;

[SetUp]
public void SetUp()
{
this.mockRepository = new MockRepository(MockBehavior.Strict);

this.mockLogger = this.mockRepository.Create<ILogger<LoRaWANDeviceModelPropertiesController>>();
this.mockMapper = this.mockRepository.Create<IMapper>();
this.mockTableClientFactory = this.mockRepository.Create<ITableClientFactory>();
this.mockDeviceTemplatesTableClient = this.mockRepository.Create<TableClient>();
this.mockDeviceModelPropertiesTableClient = this.mockRepository.Create<TableClient>();

}

private LoRaWANDeviceModelPropertiesController CreateDeviceModelPropertiesController()
{
return new LoRaWANDeviceModelPropertiesController(
this.mockLogger.Object,
this.mockMapper.Object,
this.mockTableClientFactory.Object);
}

[Test]
public async Task GetPropertiesStateUnderTestExpectedBehavior()
{
// Arrange
var deviceModelPropertiesController = this.CreateDeviceModelPropertiesController();
var entity = SetupMockEntity();
var mockResponse = this.mockRepository.Create<Response>();

_ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync<DeviceModelProperty>(
It.Is<string>(x => x == $"PartitionKey eq '{ entity.RowKey }'"),
It.IsAny<int?>(),
It.IsAny<IEnumerable<string>>(),
It.IsAny<CancellationToken>()))
.Returns(AsyncPageable<DeviceModelProperty>.FromPages(new[]
{
Page<DeviceModelProperty>.FromValues(new[]
{
new DeviceModelProperty
{
RowKey = Guid.NewGuid().ToString(),
PartitionKey = entity.RowKey
}
}, null, mockResponse.Object)
}));

_ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties())
.Returns(this.mockDeviceModelPropertiesTableClient.Object);

_ = this.mockMapper.Setup(c => c.Map<DeviceProperty>(It.Is<DeviceModelProperty>(x => x.PartitionKey == entity.RowKey)))
.Returns((DeviceModelProperty x) => new DeviceProperty
{
Name = x.Name,
});

// Act
var response = await deviceModelPropertiesController.GetProperties(entity.RowKey);

// Assert
Assert.IsAssignableFrom<OkObjectResult>(response.Result);
var okObjectResult = response.Result as ObjectResult;

Assert.NotNull(okObjectResult);
Assert.IsAssignableFrom<List<DeviceProperty>>(okObjectResult.Value);
var result = (List<DeviceProperty>)okObjectResult.Value;

Assert.NotNull(result);
Assert.AreEqual(1, result.Count);


this.mockRepository.VerifyAll();
}

[Test]
public async Task WhenDeviceModelNotExistsGetPropertiesShouldReturnHttp404()
{
// Arrange
var deviceModelPropertiesController = this.CreateDeviceModelPropertiesController();
SetupNotFoundEntity();

// Act
var response = await deviceModelPropertiesController.GetProperties(Guid.NewGuid().ToString());

// Assert
Assert.IsAssignableFrom<NotFoundResult>(response.Result);
this.mockRepository.VerifyAll();
}

[Test]
public async Task SetPropertiesStateUnderTestExpectedBehavior()
{
// Arrange
var deviceModelPropertiesController = this.CreateDeviceModelPropertiesController();
var entity = SetupMockEntity();
var mockResponse = this.mockRepository.Create<Response>();
var existingProperty = Guid.NewGuid().ToString();

var properties = new[]
{
new DeviceProperty
{
DisplayName =Guid.NewGuid().ToString(),
Name = Guid.NewGuid().ToString()
}
};

_ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync<DeviceModelProperty>(
It.Is<string>(x => x == $"PartitionKey eq '{ entity.RowKey }'"),
It.IsAny<int?>(),
It.IsAny<IEnumerable<string>>(),
It.IsAny<CancellationToken>()))
.Returns(AsyncPageable<DeviceModelProperty>.FromPages(new[]
{
Page<DeviceModelProperty>.FromValues(new DeviceModelProperty[]
{
new DeviceModelProperty
{
RowKey = existingProperty,
PartitionKey = entity.RowKey,
}
}, null, mockResponse.Object)
}));

_ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.AddEntityAsync(
It.Is<DeviceModelProperty>(x => x.PartitionKey == entity.RowKey),
It.IsAny<CancellationToken>()))
.ReturnsAsync(mockResponse.Object);

_ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.DeleteEntityAsync(
It.Is<string>(x => x == entity.RowKey),
It.Is<string>(x => x == existingProperty),
It.IsAny<ETag>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(mockResponse.Object);

_ = this.mockMapper.Setup(c => c.Map(
It.IsAny<DeviceProperty>(),
It.IsAny<Action<IMappingOperationOptions<object, DeviceModelProperty>>>()))
.Returns((DeviceProperty x, Action<IMappingOperationOptions<object, DeviceModelProperty>> a) => new DeviceModelProperty
{
Name = x.Name,
PartitionKey = entity.RowKey
});

_ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties())
.Returns(this.mockDeviceModelPropertiesTableClient.Object);

// Act
var result = await deviceModelPropertiesController.SetProperties(entity.RowKey, properties);

// Assert
Assert.IsAssignableFrom<OkResult>(result);
this.mockRepository.VerifyAll();
}

[Test]
public async Task WhenDeviceModelNotExistsSetPropertiesShouldReturnHttp404()
{
// Arrange
var deviceModelPropertiesController = this.CreateDeviceModelPropertiesController();
SetupNotFoundEntity();

// Act
var result = await deviceModelPropertiesController.SetProperties(Guid.NewGuid().ToString(), null);

// Assert
Assert.IsAssignableFrom<NotFoundResult>(result);
this.mockRepository.VerifyAll();
}

private TableEntity SetupMockEntity()
{
var mockResponse = this.mockRepository.Create<Response<TableEntity>>();
var modelId = Guid.NewGuid().ToString();
var entity = new TableEntity("0", modelId);

_ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync<TableEntity>(
It.Is<string>(p => p == "0"),
It.Is<string>(k => k == modelId),
It.IsAny<IEnumerable<string>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(mockResponse.Object);

_ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates())
.Returns(mockDeviceTemplatesTableClient.Object);

return entity;
}

private void SetupNotFoundEntity()
{
_ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync<TableEntity>(
It.Is<string>(p => p == "0"),
It.IsAny<string>(),
It.IsAny<IEnumerable<string>>(),
It.IsAny<CancellationToken>()))
.Throws(new RequestFailedException(StatusCodes.Status404NotFound, "Not Found"));

_ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates())
.Returns(mockDeviceTemplatesTableClient.Object);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10
using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Azure;
using Azure.Data.Tables;
using AzureIoTHub.Portal.Server.Entities;
using AzureIoTHub.Portal.Server.Factories;
using AzureIoTHub.Portal.Shared.Models.V10;
using Microsoft.AspNetCore.Http;
Expand All @@ -19,23 +16,8 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10
[ApiVersion("1.0")]
[Route("api/models/{id}/properties")]
[ApiExplorerSettings(GroupName = "Device Models")]
public class DeviceModelPropertiesController : Controller
public class DeviceModelPropertiesController : DeviceModelPropertiesControllerBase
{
/// <summary>
/// The table client factory.
/// </summary>
private readonly ITableClientFactory tableClientFactory;

/// <summary>
/// The logger.
/// </summary>
private readonly ILogger log;

/// <summary>
/// The mapper.
/// </summary>
private readonly IMapper mapper;

/// <summary>
/// Initializes a new instance of the Device model properties controller class.
/// </summary>
Expand All @@ -46,11 +28,9 @@ public DeviceModelPropertiesController(
ILogger<DeviceModelPropertiesController> log,
IMapper mapper,
ITableClientFactory tableClientFactory)
: base(log, mapper, tableClientFactory)
{
this.log = log;

this.mapper = mapper;
this.tableClientFactory = tableClientFactory;
}

/// <summary>
Expand All @@ -61,25 +41,9 @@ public DeviceModelPropertiesController(
[HttpGet(Name = "GET Device model properties")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<DeviceProperty>))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<DeviceProperty>>> GetProperties(string id)
public override async Task<ActionResult<IEnumerable<DeviceProperty>>> GetProperties(string id)
{
if (!(await DeviceModelExists(id)))
{
return NotFound();
}

var items = this.tableClientFactory
.GetDeviceTemplateProperties()
.QueryAsync<DeviceModelProperty>($"PartitionKey eq '{id}'");

var result = new List<DeviceProperty>();

await foreach (var item in items)
{
result.Add(this.mapper.Map<DeviceProperty>(item));
}

return this.Ok(result);
return await base.GetProperties(id);
}

/// <summary>
Expand All @@ -91,60 +55,9 @@ public async Task<ActionResult<IEnumerable<DeviceProperty>>> GetProperties(strin
[HttpPost(Name = "POST Device model properties")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> SetProperties(string id, IEnumerable<DeviceProperty> properties)
public override async Task<ActionResult> SetProperties(string id, IEnumerable<DeviceProperty> properties)
{
if (!(await DeviceModelExists(id)))
{
return NotFound();
}

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var table = this.tableClientFactory
.GetDeviceTemplateProperties();

var items = table
.QueryAsync<DeviceModelProperty>($"PartitionKey eq '{id}'");

await foreach (var item in items)
{
_ = await table.DeleteEntityAsync(id, item.RowKey);
}

foreach (var item in properties)
{
var entity = this.mapper.Map<DeviceModelProperty>(item, opts => opts.Items[nameof(DeviceModelProperty.PartitionKey)] = id);

_ = await table.AddEntityAsync(entity);
}

return this.Ok();
}

private async Task<bool> DeviceModelExists(string id)
{
try
{
var entity = await this.tableClientFactory
.GetDeviceTemplates()
.GetEntityAsync<TableEntity>("0", id);

return true;
}
catch (RequestFailedException e)
{
if (e.Status == StatusCodes.Status404NotFound)
{
return false;
}

this.log.LogError(e.Message, e);

throw;
}
return await base.SetProperties(id, properties);
}
}
}
Loading

0 comments on commit 1200105

Please sign in to comment.