Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit test to lorawan device model creation page #456

Merged
merged 1 commit into from
Mar 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,75 +1,87 @@
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using RichardSzalay.MockHttp;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
// 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.Helpers
{
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using RichardSzalay.MockHttp;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;

public static class MockHttpHelper
{
public static MockHttpMessageHandler AddMockHttpClient(this TestServiceProvider services)
{
var mockHttpHandler = new MockHttpMessageHandler();
var httpClient = mockHttpHandler.ToHttpClient();
httpClient.BaseAddress = new Uri("http://fake.local");
services.AddSingleton<HttpClient>(httpClient);

_ = services.AddSingleton<HttpClient>(httpClient);

return mockHttpHandler;
}

public static MockedRequest RespondJson<T>(this MockedRequest request, T content)
{
request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(JsonSerializer.Serialize(content));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});
_ = request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(content))
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});

return request;
}

public static MockedRequest RespondJsonAsync<T>(this MockedRequest request, Task<T> content)
{
request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(JsonSerializer.Serialize(content));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});
_ = request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(content))
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});

return request;
}

public static MockedRequest RespondText(this MockedRequest request, string content)
{
request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(content);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});
_ = request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(content)
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});

return request;
}

public static MockedRequest RespondJson<T>(this MockedRequest request, Func<T> contentProvider)
{
request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(JsonSerializer.Serialize(contentProvider()));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});
_ = request.Respond(req =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(contentProvider()))
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
});

return request;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace AzureIoTHub.Portal.Server.Tests.Unit.Pages
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Client.Pages.DeviceModels;
using AzureIoTHub.Portal.Server.Tests.Unit.Helpers;
using AzureIoTHub.Portal.Shared.Models;
Expand Down Expand Up @@ -35,6 +36,7 @@ public class CreateDeviceModelPageTests : IDisposable
private MockHttpMessageHandler mockHttpClient;

private static string apiBaseUrl => $"/api/models";
private static string lorawanApiUrl => $"/api/lorawan/models";

[SetUp]
public void SetUp()
Expand Down Expand Up @@ -282,6 +284,41 @@ public void WhenLoraFeatureIsEnabledModelDetailsShouldDisplayLoRaWANTab()
this.mockHttpClient.VerifyNoOutstandingExpectation();
}

[Test]
public void WhenLoraDeviceModelDetailsShouldCallLoRaAPIs()
{
// Arrange
_ = testContext.Services.AddSingleton(new PortalSettings { IsLoRaSupported = true });

var cut = RenderComponent<CreateDeviceModelPage>();
_ = cut.WaitForElement("#form");

cut.Find($"#{nameof(DeviceModel.Name)}").Change(Guid.NewGuid().ToString());
cut.Find($"#{nameof(DeviceModel.Description)}").Change(Guid.NewGuid().ToString());

cut.WaitForElement("#SupportLoRaFeatures")
.Change(true);

_ = this.mockHttpClient.When(HttpMethod.Post, $"{lorawanApiUrl}")
.RespondText(string.Empty);

_ = this.mockHttpClient.When(HttpMethod.Post, $"{ lorawanApiUrl }/*/commands")
.RespondText(string.Empty);

_ = this.mockHttpClient.When(HttpMethod.Post, $"{ lorawanApiUrl }/*/avatar")
.RespondText(string.Empty);

var saveButton = cut.WaitForElement("#SaveButton");

// Act
saveButton.Click();
cut.WaitForState(() => testContext.Services.GetRequiredService<FakeNavigationManager>().Uri.EndsWith("/device-models", StringComparison.OrdinalIgnoreCase));

// Assert
this.mockHttpClient.VerifyNoOutstandingExpectation();
}


public void Dispose()
{
GC.SuppressFinalize(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<MudText Typo="Typo.h5" Color="Color.Primary" Class="mb-4">Device Model</MudText>

<MudForm Model="@Model" @ref="form" id="form">
<MudTabs Elevation="1" Rounded="true" PanelClass="mt-6" @ref="tabs">
<MudTabs Elevation="1" Rounded="true" PanelClass="mt-6">
<MudTabPanel Text="General" Style=@(standardValidator.Validate(Model).IsValid ? "" : "color: red")>
<MudGrid>
<MudItem xs="12" sm="4" md="2">
Expand Down Expand Up @@ -143,13 +143,12 @@
</MudTabPanel>
@if (IsLoRa)
{
<MudTabPanel Text="LoRaWAN" Style=@(loraValidator.Validate(Model as LoRaDeviceModel).IsValid ? "" : "color: red")>
<MudTabPanel Class="LoRaWANTab" Text="LoRaWAN" Style=@(loraValidator.Validate(Model as LoRaDeviceModel).IsValid ? "" : "color: red")>
<MudGrid>
<CreateLoraDeviceModel LoRaDeviceModel="@(Model as LoRaDeviceModel)"
Commands="Commands"
OnSaveClick="Save"
CommandValidation="@CommandValidation" />

</MudGrid>
</MudTabPanel>
}
Expand Down Expand Up @@ -217,9 +216,6 @@
private MultipartFormDataContent content;
private string imageDataUrl;

// Used to switch active tab
private MudTabs tabs;

private void DeleteAvatar()
{
content = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
<MudItem xs="12">
<MudGrid>
<MudItem md="6" xs="12">
<MudSwitch @bind-Checked="@LoRaDeviceModel.UseOTAA">@(LoRaDeviceModel.UseOTAA ? "OTAA setting enable" : "APB setting enable")</MudSwitch>
</MudItem>
<MudItem md="6" xs="12">
<MudSwitch @bind-Checked="@LoRaDeviceModel.ABPRelaxMode" For="@(() => LoRaDeviceModel.ABPRelaxMode)" Label="Disable ABP relax mode" Color="Color.Primary"></MudSwitch>
<MudSwitch id="@nameof(LoRaDeviceModel.UseOTAA)" @bind-Checked="@LoRaDeviceModel.UseOTAA" Color="Color.Primary">@(LoRaDeviceModel.UseOTAA ? "OTAA setting enable" : "APB setting enable")</MudSwitch>
</MudItem>
@if (!LoRaDeviceModel.UseOTAA)
{
<MudItem md="6" xs="12">
<MudSwitch id="@nameof(LoRaDeviceModel.ABPRelaxMode)" @bind-Checked="@LoRaDeviceModel.ABPRelaxMode" For="@(() => LoRaDeviceModel.ABPRelaxMode)" Label="Disable ABP relax mode" Color="Color.Primary"></MudSwitch>
</MudItem>
}
<MudItem md="3" xs="6">
<MudSelect id="@nameof(LoRaDeviceModel.ClassType)"
@bind-Value="@LoRaDeviceModel.ClassType"
Expand All @@ -32,7 +35,7 @@
</MudSelect>
</MudItem>
<MudItem md="3" xs="6">
<MudSelect @bind-Value="@LoRaDeviceModel.Deduplication" For="@(() => LoRaDeviceModel.Deduplication)" AnchorOrigin="Origin.BottomCenter" Label="Message Deduplication" Variant="Variant.Outlined">
<MudSelect id="@nameof(LoRaDeviceModel.Deduplication)" @bind-Value="@LoRaDeviceModel.Deduplication" For="@(() => LoRaDeviceModel.Deduplication)" AnchorOrigin="Origin.BottomCenter" Label="Message Deduplication" Variant="Variant.Outlined">
@foreach (DeduplicationMode item in Enum.GetValues(typeof(DeduplicationMode)))
{
<MudSelectItem Value="@item">@item</MudSelectItem>
Expand All @@ -46,18 +49,18 @@
@if (LoRaDeviceModel.UseOTAA)
{
<MudItem xs="12">
<MudTextField @bind-Value="@LoRaDeviceModel.AppEUI" Label="OTAA AppEUI" For="@(() => LoRaDeviceModel.AppEUI)" Variant="Variant.Outlined" Required="true"></MudTextField>
<MudTextField id="@nameof(LoRaDeviceModel.AppEUI)" @bind-Value="@LoRaDeviceModel.AppEUI" Label="OTAA AppEUI" For="@(() => LoRaDeviceModel.AppEUI)" Variant="Variant.Outlined" Required="true"></MudTextField>
</MudItem>
}
<MudItem xs="12">
<MudTextField @bind-Value="@LoRaDeviceModel.SensorDecoder" Label="Sensor Decoder URL" For="@(() => LoRaDeviceModel.SensorDecoder)" Variant="Variant.Outlined"></MudTextField>
<MudTextField id="@nameof(LoRaDeviceModel.SensorDecoder)" @bind-Value="@LoRaDeviceModel.SensorDecoder" Label="Sensor Decoder URL" For="@(() => LoRaDeviceModel.SensorDecoder)" Variant="Variant.Outlined"></MudTextField>
</MudItem>
</MudGrid>
</MudItem>
<MudItem md="6" xs=12>
<MudGrid>
<MudItem md="6" xs="12">
<MudNumericField @bind-Value="@LoRaDeviceModel.KeepAliveTimeout" For="@(() => LoRaDeviceModel.KeepAliveTimeout)" Min=1 Label="Device Connection Timeout" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.KeepAliveTimeout)" @bind-Value="@LoRaDeviceModel.KeepAliveTimeout" For="@(() => LoRaDeviceModel.KeepAliveTimeout)" Min=1 Label="Device Connection Timeout" Variant="Variant.Outlined"></MudNumericField>
</MudItem>

</MudGrid>
Expand All @@ -71,21 +74,21 @@
<MudText Typo=Typo.h5 Class="mb-4">Receive Windows</MudText>
<MudGrid>
<MudItem xs="12">
<MudSwitch @bind-Checked="@LoRaDeviceModel.Downlink" For="@(() => LoRaDeviceModel.Downlink)" Label="Enable/disable downstream messages" Color="Color.Primary"></MudSwitch>
<MudSwitch id="@nameof(LoRaDeviceModel.Downlink)" @bind-Checked="@LoRaDeviceModel.Downlink" For="@(() => LoRaDeviceModel.Downlink)" Label="Enable/disable downstream messages" Color="Color.Primary"></MudSwitch>
</MudItem>
@if (LoRaDeviceModel.Downlink.HasValue && LoRaDeviceModel.Downlink.Value)
{
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.PreferredWindow" For="@(() => LoRaDeviceModel.PreferredWindow)" Min="1" Max="2" Label="Preferred receive window" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.PreferredWindow)" @bind-Value="@LoRaDeviceModel.PreferredWindow" For="@(() => LoRaDeviceModel.PreferredWindow)" Min="1" Max="2" Label="Preferred receive window" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.RXDelay" For="@(() => LoRaDeviceModel.RXDelay)" Min=1 Label="RX Delay" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.RXDelay)" @bind-Value="@LoRaDeviceModel.RXDelay" For="@(() => LoRaDeviceModel.RXDelay)" Min=1 Label="RX Delay" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.RX1DROffset" For="@(() => LoRaDeviceModel.RX1DROffset)" Label="RX1 Datarate Offset" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.RX1DROffset)" @bind-Value="@LoRaDeviceModel.RX1DROffset" For="@(() => LoRaDeviceModel.RX1DROffset)" Label="RX1 Datarate Offset" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.RX2DataRate" For="@(() => LoRaDeviceModel.RX2DataRate)" Label="RX2 Datarate" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.RX2DataRate)" @bind-Value="@LoRaDeviceModel.RX2DataRate" For="@(() => LoRaDeviceModel.RX2DataRate)" Label="RX2 Datarate" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
}
</MudGrid>
Expand All @@ -95,18 +98,18 @@
<MudText Typo=Typo.h5 Class="mb-4">Frame Counters</MudText>
<MudGrid>
<MudItem xs="12">
<MudSwitch @bind-Checked="@LoRaDeviceModel.Supports32BitFCnt" For="@(() => LoRaDeviceModel.Supports32BitFCnt)" Label="32bit counter support" Color="Color.Primary" Variant="Variant.Outlined"></MudSwitch>
<MudSwitch id="@nameof(LoRaDeviceModel.Supports32BitFCnt)" @bind-Checked="@LoRaDeviceModel.Supports32BitFCnt" For="@(() => LoRaDeviceModel.Supports32BitFCnt)" Label="32bit counter support" Color="Color.Primary" Variant="Variant.Outlined"></MudSwitch>
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.FCntUpStart" For="@(() => LoRaDeviceModel.FCntUpStart)" Min=0 Label="Frame counter up start value" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.FCntUpStart)" @bind-Value="@LoRaDeviceModel.FCntUpStart" For="@(() => LoRaDeviceModel.FCntUpStart)" Min=0 Label="Frame counter up start value" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
@if (LoRaDeviceModel.Downlink.HasValue && LoRaDeviceModel.Downlink.Value)
{
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.FCntDownStart" For="@(() => LoRaDeviceModel.FCntDownStart)" Min=0 Label="Frame counter down start value" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.FCntDownStart)" @bind-Value="@LoRaDeviceModel.FCntDownStart" For="@(() => LoRaDeviceModel.FCntDownStart)" Min=0 Label="Frame counter down start value" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField @bind-Value="@LoRaDeviceModel.FCntResetCounter" For="@(() => LoRaDeviceModel.FCntResetCounter)" Min=0 Label="Frame counter reset counter value" Variant="Variant.Outlined"></MudNumericField>
<MudNumericField id="@nameof(LoRaDeviceModel.FCntResetCounter)" @bind-Value="@LoRaDeviceModel.FCntResetCounter" For="@(() => LoRaDeviceModel.FCntResetCounter)" Min=0 Label="Frame counter reset counter value" Variant="Variant.Outlined"></MudNumericField>
</MudItem>
}
</MudGrid>
Expand Down Expand Up @@ -157,7 +160,7 @@
}
</MudCardContent>
<MudCardActions Class="pb-4 pl-4">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="OnSaveClick">Save Changes</MudButton>
<MudButton id="SaveButton" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="OnSaveClick">Save Changes</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ public class LoRaDeviceModelValidator : AbstractValidator<LoRaDeviceModel>
{
public LoRaDeviceModelValidator()
{
_ = RuleFor(x => x.AppEUI)
.NotEmpty()
.Length(1, 100)
.When(x => x.UseOTAA);
//_ = RuleFor(x => x.AppEUI)
// .NotEmpty()
// .Length(1, 100)
// .When(x => x.UseOTAA);
}
}
}