Skip to content
Merged
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
Expand Up @@ -725,6 +725,343 @@ public void MerchantsEdit_DisplaysAssignedContracts_WhenPresent()
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Test Contract"), timeout: TimeSpan.FromSeconds(5));
}

[Fact]
public void MerchantsEdit_LoadMerchant_Success_LoadsAllData()
{
// Arrange
var merchantId = Guid.NewGuid();
SetupSuccessfulDataLoad(merchantId, "Test Merchant", "MERCH001");

// Act
IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Test Merchant"), timeout: TimeSpan.FromSeconds(5));
cut.Markup.ShouldContain("MERCH001");
this.MerchantUIService.Verify(m => m.GetMerchant(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId), Times.Once);
this.MerchantUIService.Verify(m => m.GetMerchantOperators(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId), Times.Once);
this.MerchantUIService.Verify(m => m.GetMerchantContracts(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId), Times.Once);
this.MerchantUIService.Verify(m => m.GetMerchantDevices(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId), Times.Once);
}

[Fact]
public void MerchantsEdit_AddDeviceToMerchant_Success_ShowsSuccessMessage()
{
// Arrange
var merchantId = Guid.NewGuid();
SetupSuccessfulDataLoad(merchantId);

this.MerchantUIService.Setup(m => m.AddMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<String>()))
.ReturnsAsync(Result.Success);

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Add Device button
IElement addDeviceButton = cut.Find("#addDeviceButton");
addDeviceButton.Click();

// Act - Enter device identifier and add
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement deviceInput = cut.Find("input[placeholder*='device']");
deviceInput.Change("DEV123");

IElement addButton = cut.FindAll("button")
.First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addDeviceButton");
addButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Device added successfully"), timeout: TimeSpan.FromSeconds(10));
this.MerchantUIService.Verify(m => m.AddMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, "DEV123"), Times.Once);
}

[Fact]
public void MerchantsEdit_AddDeviceToMerchant_Failure_ShowsErrorMessage()
{
// Arrange
var merchantId = Guid.NewGuid();
SetupSuccessfulDataLoad(merchantId);

this.MerchantUIService.Setup(m => m.AddMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<String>()))
.ReturnsAsync(Result.Failure());

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Add Device button
IElement addDeviceButton = cut.Find("#addDeviceButton");
addDeviceButton.Click();

// Act - Enter device identifier and add
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement deviceInput = cut.Find("input[placeholder*='device']");
deviceInput.Change("DEV123");

IElement addButton = cut.FindAll("button")
.First(b => b.TextContent.Trim() == "Add" && (b.GetAttribute("id") ?? "") != "addDeviceButton");
addButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to add device"), timeout: TimeSpan.FromSeconds(5));
}

[Fact]
public void MerchantsEdit_SaveAllChanges_Success_ShowsSuccessMessageAndNavigates()
{
// Arrange
var merchantId = Guid.NewGuid();
SetupSuccessfulDataLoad(merchantId);

this.MerchantUIService.Setup(m => m.UpdateMerchant(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<MerchantModels.MerchantEditModel>()))
.ReturnsAsync(Result.Success);

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Act - Find and click save button (button text is "Save" not "Save Changes")
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button[type='submit']");
IElement? saveButton = buttons.FirstOrDefault();
saveButton.ShouldNotBeNull();
saveButton.Click();

// Assert - Component has WaitOnUIRefresh() with 2.5s delay before navigation
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Merchant details updated successfully"), timeout: TimeSpan.FromSeconds(10));
cut.WaitForState(() => _fakeNavigationManager.Uri.Contains("/merchants"), TimeSpan.FromSeconds(10));
_fakeNavigationManager.Uri.ShouldContain("/merchants");
this.MerchantUIService.Verify(m => m.UpdateMerchant(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<MerchantModels.MerchantEditModel>()), Times.Once);
}

[Fact]
public void MerchantsEdit_SaveAllChanges_Failure_ShowsErrorMessage()
{
// Arrange
var merchantId = Guid.NewGuid();
SetupSuccessfulDataLoad(merchantId);

this.MerchantUIService.Setup(m => m.UpdateMerchant(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<MerchantModels.MerchantEditModel>()))
.ReturnsAsync(Result.Failure());

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Act - Find and click save button (button text is "Save" not "Save Changes")
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button[type='submit']");
IElement? saveButton = buttons.FirstOrDefault();
saveButton.ShouldNotBeNull();
saveButton.Click();

// Assert - Use longer timeout to account for async operations
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Failed to update merchant"), timeout: TimeSpan.FromSeconds(10));
this.MerchantUIService.Verify(m => m.UpdateMerchant(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, It.IsAny<MerchantModels.MerchantEditModel>()), Times.Once);
}

[Fact]
public void MerchantsEdit_SwapDeviceConfirm_Success_ShowsSuccessMessage()
{
// Arrange
var merchantId = Guid.NewGuid();
var existingDevice = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV123" };
SetupSuccessfulDataLoad(merchantId, assignedDevices: new List<MerchantModels.MerchantDeviceModel> { existingDevice });

this.MerchantUIService.Setup(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, "DEV123", "DEV456"))
.ReturnsAsync(Result.Success);

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Swap button for the device
cut.WaitForAssertion(() => cut.FindAll("button").Any(b => b.TextContent.Contains("Swap")).ShouldBeTrue(), timeout: TimeSpan.FromSeconds(5));
IElement swapButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Swap"));
swapButton.Click();

// Act - Enter new device identifier and confirm swap
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement swapInput = cut.FindAll("input").Last();
swapInput.Change("DEV456");

IElement confirmButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Confirm"));
confirmButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("Device DEV123 swapped for DEV456"), timeout: TimeSpan.FromSeconds(5));
this.MerchantUIService.Verify(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, "DEV123", "DEV456"), Times.Once);
}

[Fact]
public void MerchantsEdit_SwapDeviceConfirm_EmptyIdentifier_ShowsError()
{
// Arrange
var merchantId = Guid.NewGuid();
var existingDevice = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV123" };
SetupSuccessfulDataLoad(merchantId, assignedDevices: new List<MerchantModels.MerchantDeviceModel> { existingDevice });

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Swap button for the device
cut.WaitForAssertion(() => cut.FindAll("button").Any(b => b.TextContent.Contains("Swap")).ShouldBeTrue(), timeout: TimeSpan.FromSeconds(5));
IElement swapButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Swap"));
swapButton.Click();

// Act - Try to confirm swap without entering identifier
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement confirmButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Confirm"));
confirmButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New device identifier is required"), timeout: TimeSpan.FromSeconds(5));
this.MerchantUIService.Verify(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<String>()), Times.Never);
}

[Fact]
public void MerchantsEdit_SwapDeviceConfirm_SameIdentifier_ShowsError()
{
// Arrange
var merchantId = Guid.NewGuid();
var existingDevice = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV123" };
SetupSuccessfulDataLoad(merchantId, assignedDevices: new List<MerchantModels.MerchantDeviceModel> { existingDevice });

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Swap button for the device
cut.WaitForAssertion(() => cut.FindAll("button").Any(b => b.TextContent.Contains("Swap")).ShouldBeTrue(), timeout: TimeSpan.FromSeconds(5));
IElement swapButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Swap"));
swapButton.Click();

// Act - Enter same device identifier and try to confirm swap
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement swapInput = cut.FindAll("input").Last();
swapInput.Change("DEV123");

IElement confirmButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Confirm"));
confirmButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New device identifier cannot be the same as the current device"), timeout: TimeSpan.FromSeconds(5));
this.MerchantUIService.Verify(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<String>()), Times.Never);
}

[Fact]
public void MerchantsEdit_SwapDeviceConfirm_DuplicateIdentifier_ShowsError()
{
// Arrange
var merchantId = Guid.NewGuid();
var device1 = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV123" };
var device2 = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV456" };
SetupSuccessfulDataLoad(merchantId, assignedDevices: new List<MerchantModels.MerchantDeviceModel> { device1, device2 });

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Swap button for the first device
cut.WaitForAssertion(() => cut.FindAll("button").Any(b => b.TextContent.Contains("Swap")).ShouldBeTrue(), timeout: TimeSpan.FromSeconds(5));
IElement swapButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Swap"));
swapButton.Click();

// Act - Try to swap to an already assigned device identifier
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));
IElement swapInput = cut.FindAll("input").Last();
swapInput.Change("DEV456");

IElement confirmButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Confirm"));
confirmButton.Click();

// Assert
cut.WaitForAssertion(() => cut.Markup.ShouldContain("The specified device identifier is already assigned"), timeout: TimeSpan.FromSeconds(5));
this.MerchantUIService.Verify(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<String>(), It.IsAny<String>()), Times.Never);
}

[Fact]
public void MerchantsEdit_SwapDeviceConfirm_Failure_ClearsSwapUI()
{
// Arrange
var merchantId = Guid.NewGuid();
var existingDevice = new MerchantModels.MerchantDeviceModel { DeviceIdentifier = "DEV123" };
SetupSuccessfulDataLoad(merchantId, assignedDevices: new List<MerchantModels.MerchantDeviceModel> { existingDevice });

this.MerchantUIService.Setup(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, "DEV123", "DEV456"))
.ReturnsAsync(Result.Failure());

IRenderedComponent<MerchantsEdit> cut = RenderComponent<MerchantsEdit>(parameters => parameters
.Add(p => p.MerchantId, merchantId));
cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5));

// Switch to devices tab
IRefreshableElementCollection<IElement> buttons = cut.FindAll("button");
IElement? devicesButton = buttons.FirstOrDefault(b => b.TextContent.Contains("Assigned Devices"));
devicesButton?.Click();

// Click Swap button for the device
cut.WaitForAssertion(() => cut.FindAll("button").Any(b => b.TextContent.Contains("Swap")).ShouldBeTrue(), timeout: TimeSpan.FromSeconds(5));
IElement swapButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Swap"));
swapButton.Click();

// Verify swap UI is shown
cut.WaitForAssertion(() => cut.Markup.ShouldContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));

// Act - Enter new device identifier and confirm swap
IElement swapInput = cut.FindAll("input").Last();
swapInput.Change("DEV456");

IElement confirmButton = cut.FindAll("button")
.First(b => b.TextContent.Contains("Confirm"));
confirmButton.Click();

// Assert - Component calls CancelSwapDevice() which clears the swap UI even on failure
// This means "New Device Identifier" should no longer be in markup after the operation
cut.WaitForAssertion(() => cut.Markup.ShouldNotContain("New Device Identifier"), timeout: TimeSpan.FromSeconds(5));
this.MerchantUIService.Verify(m => m.SwapMerchantDevice(It.IsAny<CorrelationId>(), It.IsAny<Guid>(), merchantId, "DEV123", "DEV456"), Times.Once);
}

// Helper methods
private void SetupSuccessfulDataLoad(Guid merchantId,
string merchantName = "Test Merchant",
Expand Down
Loading