Skip to content

Commit

Permalink
Handle ProblemDetailsExceptions on frontend : Device Tags (#836)
Browse files Browse the repository at this point in the history
* Handle ProblemDetailsExceptions on Device Tags

* Fix unit tests and remove unnecessary Thread.sleep()

* Fix JSInterop issue

* Remove response.EnsureSuccessStatusCode()

* Fix JSInterop issue on DeviceModelDetailsPageTests
  • Loading branch information
audserraCGI authored Jun 21, 2022
1 parent 1d25ac9 commit ddc42fc
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public void SetUp()
_ = this.testContext.JSInterop.SetupVoid("Blazor._internal.InputFile.init", _ => true);
_ = this.testContext.JSInterop.Setup<BoundingClientRect>("mudElementRef.getBoundingClientRect", _ => true);
_ = this.testContext.JSInterop.Setup<IEnumerable<BoundingClientRect>>("mudResizeObserver.connect", _ => true);
_ = this.testContext.JSInterop.SetupVoid("mudElementRef.restoreFocus", _ => true);

this.mockNavigationManager = this.testContext.Services.GetRequiredService<FakeNavigationManager>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ namespace AzureIoTHub.Portal.Server.Tests.Unit
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using AzureIoTHub.Portal.Client.Exceptions;
using AzureIoTHub.Portal.Client.Models;
using AzureIoTHub.Portal.Client.Pages.Settings;
using AzureIoTHub.Portal.Client.Shared;
using AzureIoTHub.Portal.Models.v10;
using AzureIoTHub.Portal.Server.Tests.Unit.Helpers;
using Bunit;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Moq;
Expand All @@ -31,7 +31,6 @@ public class DeviceTagsPageTests : IDisposable
private MockRepository mockRepository;
private Mock<IDialogService> mockDialogService;
private Mock<ISnackbar> mockSnackbarService;
private FakeNavigationManager mockNavigationManager;

private static string ApiBaseUrl => "/api/settings/device-tags";

Expand All @@ -57,8 +56,7 @@ public void SetUp()
_ = this.testContext.JSInterop.SetupVoid("mudPopover.connect", _ => true);
_ = this.testContext.JSInterop.Setup<BoundingClientRect>("mudElementRef.getBoundingClientRect", _ => true);
_ = this.testContext.JSInterop.Setup<IEnumerable<BoundingClientRect>>("mudResizeObserver.connect", _ => true);

this.mockNavigationManager = this.testContext.Services.GetRequiredService<FakeNavigationManager>();
_ = this.testContext.JSInterop.SetupVoid("mudElementRef.restoreFocus", _ => true);

this.mockHttpClient.AutoFlush = true;
}
Expand All @@ -69,6 +67,61 @@ private IRenderedComponent<TComponent> RenderComponent<TComponent>(params Compon
return this.testContext.RenderComponent<TComponent>(parameters);
}

[Test]
public void DeviceListPageRendersCorrectly()
{
// Arrange
_ = this.mockHttpClient.When(HttpMethod.Get, $"{ApiBaseUrl}")
.RespondJson(new List<DeviceTag>(){
new DeviceTag
{ Label = Guid.NewGuid().ToString(), Name = Guid.NewGuid().ToString(), Required = false, Searchable = false },
new DeviceTag
{ Label = Guid.NewGuid().ToString(), Name = Guid.NewGuid().ToString(), Required = false, Searchable = false }
});

// Act
var cut = RenderComponent<DeviceTagsPage>();
cut.WaitForAssertion(() => cut.Find("div.mud-grid"));
var grid = cut.Find("div.mud-grid");

// Assert
Assert.IsNotNull(cut.Markup);
Assert.AreEqual("Tags", cut.Find(".mud-typography-h6").TextContent);
Assert.IsNotNull(grid.InnerHtml);
Assert.AreEqual(4, cut.FindAll("tr").Count);
Assert.IsNotNull(cut.Find(".mud-table-container"));

cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingExpectation());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}

[Test]
public void OnInitializedAsyncShouldProcessProblemDetailsExceptionWhenIssueOccursOnGettingDeviceTags()
{
// Arrange
_ = this.mockHttpClient.When(HttpMethod.Get, $"{ApiBaseUrl}")
.Throw(new ProblemDetailsException(new ProblemDetailsWithExceptionDetails()));

// Act
var cut = RenderComponent<DeviceTagsPage>();
cut.WaitForAssertion(() => cut.Find("div.mud-grid"));
var grid = cut.Find("div.mud-grid");

// Assert
Assert.IsNotNull(cut.Markup);
Assert.AreEqual("Tags", cut.Find(".mud-typography-h6").TextContent);
Assert.IsNotNull(grid.InnerHtml);
Assert.AreEqual(3, cut.FindAll("tr").Count);
Assert.IsNotNull(cut.Find(".mud-table-container"));

// Assert
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingExpectation());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}


[Test]
public void ClickOnSaveShouldUpdateTagList()
{
Expand Down Expand Up @@ -118,15 +171,14 @@ public void ClickOnSaveShouldUpdateTagList()


var cut = RenderComponent<DeviceTagsPage>();

var saveButton = cut.WaitForElement("#saveButton");
cut.WaitForAssertion(() => cut.Find("#saveButton"));
var saveButton = cut.Find("#saveButton");

// Act
saveButton.Click();
Thread.Sleep(1000);

this.mockHttpClient.VerifyNoOutstandingExpectation();
this.mockRepository.VerifyAll();
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}

[Test]
Expand Down Expand Up @@ -181,14 +233,14 @@ public void ClickOnSaveShouldDisplayErrorSnackbarIfDuplicated()

var cut = RenderComponent<DeviceTagsPage>();

var saveButton = cut.WaitForElement("#saveButton");
cut.WaitForAssertion(() => cut.Find("#saveButton"));
var saveButton = cut.Find("#saveButton");

// Act
saveButton.Click();
Thread.Sleep(1000);

this.mockHttpClient.VerifyNoOutstandingExpectation();
this.mockRepository.VerifyAll();
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}

[Test]
Expand Down Expand Up @@ -243,14 +295,43 @@ public void ClickOnSaveShouldDisplayErrorSnackbarIfValidationIssue()

var cut = RenderComponent<DeviceTagsPage>();

var saveButton = cut.WaitForElement("#saveButton");
cut.WaitForAssertion(() => cut.Find("#saveButton"));
var saveButton = cut.Find("#saveButton");

// Act
saveButton.Click();
Thread.Sleep(1000);

this.mockHttpClient.VerifyNoOutstandingExpectation();
this.mockRepository.VerifyAll();
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}

[Test]
public void ClickOnSaveShouldProcessProblemDetailsExceptionIfIssueOccursWhenUpdatingDeviceTags()
{
// Arrange
_ = this.mockHttpClient.When(HttpMethod.Get, $"{ApiBaseUrl}")
.RespondJson(new List<DeviceTag>(){
new DeviceTag
{ Label = "Label", Name = "Name", Required = false, Searchable = false }
});

_ = this.mockHttpClient.When(HttpMethod.Post, $"{ApiBaseUrl}")
.Throw(new ProblemDetailsException(new ProblemDetailsWithExceptionDetails()));

var mockDialogReference = new DialogReference(Guid.NewGuid(), this.mockDialogService.Object);
_ = this.mockDialogService.Setup(c => c.Show<ProcessingDialog>(It.IsAny<string>(), It.IsAny<DialogParameters>()))
.Returns(mockDialogReference);
_ = this.mockDialogService.Setup(c => c.Close(It.Is<DialogReference>(x => x == mockDialogReference)));

// Act
var cut = RenderComponent<DeviceTagsPage>();
cut.WaitForAssertion(() => cut.Find("#saveButton"));
var saveButton = cut.Find("#saveButton");
saveButton.Click();

// Assert
cut.WaitForAssertion(() => this.mockHttpClient.VerifyNoOutstandingRequest());
cut.WaitForAssertion(() => this.mockRepository.VerifyAll());
}

public void Dispose()
Expand Down
49 changes: 35 additions & 14 deletions src/AzureIoTHub.Portal/Client/Pages/Settings/DeviceTagsPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@inject ISnackbar Snackbar
@inject IDialogService DialogService


<MudGrid>
<MudItem xs="12">
<MudTable Items=@Tags Loading="IsLoading" Dense=true Hover=true Bordered=true Striped=true>

Expand Down Expand Up @@ -76,17 +76,33 @@
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="Save" id="saveButton">Save Changes</MudButton>
</MudCardActions>
</MudItem>
</MudGrid>

@code {
[CascadingParameter]
public Error Error {get; set;}

List<DeviceTag> Tags { get; set; } = new();
private MudForm FormName { get; set; }
private MudForm FormLabel { get; set; }
private bool IsLoading { get; set; } = true;
private bool IsLoading { get; set; }

protected override async Task OnInitializedAsync()
{
Tags.AddRange(await HttpClient.GetFromJsonAsync<List<DeviceTag>>($"/api/settings/device-tags"));
try
{
IsLoading = true;
Tags.AddRange(await HttpClient.GetFromJsonAsync<List<DeviceTag>>($"/api/settings/device-tags"));
}
catch (ProblemDetailsException exception)
{
Error?.ProcessProblemDetails(exception);
}
finally
{
IsLoading = false;
}

IsLoading = false;
}

private void AddTag()
Expand Down Expand Up @@ -126,18 +142,23 @@
duplicated = true;
}

var test = FormLabel.IsValid;
var test2 = FormName.IsValid;

if (FormLabel.IsValid && FormName.IsValid && !duplicated)
{
var response = await HttpClient.PostAsJsonAsync($"/api/settings/device-tags", Tags);
response.EnsureSuccessStatusCode();

DialogService.Close(processingDialog);

// Prompts a snack bar to inform the action was successful
Snackbar.Add($"Settings have been successfully updated!", Severity.Success);
try
{
var response = await HttpClient.PostAsJsonAsync($"/api/settings/device-tags", Tags);

// Prompts a snack bar to inform the action was successful
Snackbar.Add($"Settings have been successfully updated!", Severity.Success);
}
catch (ProblemDetailsException exception)
{
Error?.ProcessProblemDetails(exception);
}
finally
{
DialogService.Close(processingDialog);
}
}
else
{
Expand Down

0 comments on commit ddc42fc

Please sign in to comment.