Skip to content
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
30 changes: 18 additions & 12 deletions src/NetworkInfrastructure.Web/Controllers/NetworkController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public async Task<IActionResult> Login(UserDto user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Name, user.UserName!),
//new Claim("FullName", user.UserName),
new Claim(ClaimTypes.Role, "Administrator"),
};
Expand Down Expand Up @@ -135,11 +135,11 @@ public async Task<IActionResult> Index(string search)

if (!string.IsNullOrEmpty(search))
{
query = query.Where(x => x.ServerName.Contains(search) ||
x.Ip.Contains(search) ||
x.Description.Contains(search) ||
x.ServerPort.Contains(search) ||
x.ServiceOwner.Contains(search)).ToList();
query = query.Where(x => (x.ServerName?.Contains(search) ?? false) ||
(x.Ip?.Contains(search) ?? false) ||
(x.Description?.Contains(search) ?? false) ||
(x.ServerPort?.Contains(search) ?? false) ||
(x.ServiceOwner?.Contains(search) ?? false)).ToList();
}

var mapper = _mapper.Map<List<NetworkAssetDto>>(query);
Expand All @@ -155,6 +155,7 @@ public IActionResult Create()
[HttpPost]
public async Task<IActionResult> Create(NetworkAssetDto model)
{
// Test recompilation
_logger.LogInformation("Attempting to create network asset with ServerName: {ServerName}", model.ServerName);
try
{
Expand All @@ -167,9 +168,14 @@ public async Task<IActionResult> Create(NetworkAssetDto model)
return View(model);
}

model.UserName = string.IsNullOrEmpty(ClaimTypes.Name)
? throw new ArgumentNullException(nameof(User))
: User.FindFirstValue(claimType: ClaimTypes.Name);
model.UserName = User.FindFirstValue(claimType: ClaimTypes.Name);

if (string.IsNullOrEmpty(model.UserName))
{
_logger.LogError("User name claim is missing, cannot create network asset.");
// Or handle as a bad request, or redirect to login, etc.
throw new InvalidOperationException("User name claim is missing. Cannot create network asset.");
}

var mapper = _mapper.Map<NetworkAsset>(model);

Expand All @@ -193,7 +199,7 @@ public async Task<IActionResult> Create(NetworkAssetDto model)

public async Task<IActionResult> Details(Guid id)
{
if (id == null || id == Guid.Empty)
if (id == Guid.Empty)
{
throw new ArgumentNullException(nameof(id));
}
Expand All @@ -212,7 +218,7 @@ public async Task<IActionResult> Details(Guid id)

public async Task<IActionResult> Delete(Guid id)
{
if (id == null || id == Guid.Empty)
if (id == Guid.Empty)
{
_logger.LogWarning("DeleteAsync called with null or empty ID.");
throw new ArgumentNullException(nameof(id));
Expand All @@ -235,7 +241,7 @@ public async Task<IActionResult> Delete(Guid id)

public async Task<IActionResult> Edit(Guid id)
{
if (id == null || id == Guid.Empty)
if (id == Guid.Empty)
{
throw new ArgumentNullException(nameof(id));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class NetworkContext : DbContext
{
public NetworkContext(DbContextOptions<NetworkContext> option) : base(option) { }

public DbSet<NetworkAsset> NetworkAssets { get; set; }
public DbSet<NetworkAsset> NetworkAssets { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public interface INetworkService
{
Task<List<NetworkAsset>> GetAllAsync();

Task<NetworkAsset> GetAsync(Guid id);
Task<NetworkAsset?> GetAsync(Guid id);

Task AddAsync(NetworkAsset entity);

Expand Down
6 changes: 3 additions & 3 deletions src/NetworkInfrastructure.Web/Data/Services/NetworkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task AddAsync(NetworkAsset entity)

public async Task DeleteAsync(Guid id)
{
if (id == null || id == Guid.Empty)
if (id == Guid.Empty)
{
throw new ArgumentNullException(nameof(id));
}
Expand Down Expand Up @@ -61,9 +61,9 @@ public async Task<List<NetworkAsset>> GetAllAsync()
return await _context.NetworkAssets.AsNoTracking().ToListAsync();
}

public async Task<NetworkAsset> GetAsync(Guid id)
public async Task<NetworkAsset?> GetAsync(Guid id)
{
if(id ==null || id == Guid.Empty)
if(id == Guid.Empty)
{
throw new ArgumentNullException(nameof(id));
}
Expand Down
2 changes: 1 addition & 1 deletion src/NetworkInfrastructure.Web/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</li>
</ul>

@if (User.Identity.IsAuthenticated)
@if (User.Identity != null && User.Identity.IsAuthenticated)
{
<form class="d-flex" method="get" asp-action="Index" asp-controller="Network">
@Html.AntiForgeryToken()
Expand Down
1 change: 1 addition & 0 deletions tests/NetworkInfrastructure.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;
173 changes: 173 additions & 0 deletions tests/NetworkInfrastructure.Tests/NetworkControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using AutoMapper;
using FluentAssertions;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using NetworkInfrastructure.Web.Controllers;
using NetworkInfrastructure.Web.Data.Entities;
using NetworkInfrastructure.Web.Data.Services;
using NetworkInfrastructure.Web.Models;
using System.Security.Claims;

namespace NetworkInfrastructure.Tests
{
[TestClass]
public class NetworkControllerTests
{
private Mock<INetworkService> _mockNetworkService = null!;
private Mock<IUserService> _mockUserService = null!;
private Mock<IValidator<UserDto>> _mockUserValidator = null!;
private Mock<IValidator<NetworkAssetDto>> _mockNetAssetValidator = null!;
private Mock<IConfiguration> _mockConfiguration = null!;
private Mock<IMapper> _mockMapper = null!;
private Mock<ILogger<NetworkController>> _mockLogger = null!;
private NetworkController _controller = null!;

[TestInitialize]
public void Setup()
{
_mockNetworkService = new Mock<INetworkService>();
_mockUserService = new Mock<IUserService>();
_mockUserValidator = new Mock<IValidator<UserDto>>();
_mockNetAssetValidator = new Mock<IValidator<NetworkAssetDto>>();
_mockConfiguration = new Mock<IConfiguration>();
_mockMapper = new Mock<IMapper>();
_mockLogger = new Mock<ILogger<NetworkController>>();

_controller = new NetworkController(
_mockNetworkService.Object,
_mockUserService.Object,
_mockUserValidator.Object,
_mockConfiguration.Object,
_mockMapper.Object,
_mockNetAssetValidator.Object,
_mockLogger.Object
);

// Setup default valid validation result
var validValidationResult = new ValidationResult();
_mockNetAssetValidator.Setup(v => v.Validate(It.IsAny<NetworkAssetDto>()))
.Returns(validValidationResult);

// Setup default mapper behavior
_mockMapper.Setup(m => m.Map<NetworkAsset>(It.IsAny<NetworkAssetDto>()))
.Returns((NetworkAssetDto dto) => new NetworkAsset { ServerName = dto.ServerName }); // Simple map for testing

// Setup HttpContext and User claims
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "testuser")
}, "mock"));
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
}

[TestMethod]
public async Task Create_Post_WithValidModel_ReturnsRedirectToIndex()
{
// Arrange
var networkAssetDto = new NetworkAssetDto { ServerName = "TestServer", Ip = "1.2.3.4" };
var networkAsset = new NetworkAsset { Id = Guid.NewGuid(), ServerName = "TestServer" };

_mockMapper.Setup(m => m.Map<NetworkAsset>(networkAssetDto))
.Returns(networkAsset);
_mockNetworkService.Setup(s => s.AddAsync(networkAsset))
.Returns(Task.CompletedTask);

// Act
var result = await _controller.Create(networkAssetDto);

// Assert
var redirectResult = result.Should().BeOfType<RedirectToActionResult>().Subject;
redirectResult.ActionName.Should().Be("Index");
_mockNetworkService.Verify(s => s.AddAsync(networkAsset), Times.Once);
// Verify logger messages if necessary (can be complex, optional for now)
}

[TestMethod]
public async Task Create_Post_WithInvalidModel_ReturnsViewWithModelError()
{
// Arrange
var networkAssetDto = new NetworkAssetDto { ServerName = "TestServer" }; // Assume IP is required for invalid state
var validationResult = new ValidationResult(new[] { new ValidationFailure("Ip", "IP Address is required") });

_mockNetAssetValidator.Setup(v => v.Validate(networkAssetDto))
.Returns(validationResult);

// Ensure ModelState is clear for this test if it's shared or modified by controller
_controller.ModelState.Clear();

// Act
var result = await _controller.Create(networkAssetDto);

// Assert
var viewResult = result.Should().BeOfType<ViewResult>().Subject;
viewResult.Model.Should().Be(networkAssetDto);
_controller.ModelState.IsValid.Should().BeFalse();
_controller.ModelState.Should().Contain(kv => kv.Key == "Ip" && kv.Value.Errors.Any(e => e.ErrorMessage == "IP Address is required"));
_mockNetworkService.Verify(s => s.AddAsync(It.IsAny<NetworkAsset>()), Times.Never);
}

[TestMethod]
public async Task Create_Post_WhenUserClaimIsMissing_ReturnsErrorView()
{
// Arrange
var networkAssetDto = new NetworkAssetDto { ServerName = "TestServer", Ip = "1.2.3.4" };
// Simulate User.FindFirstValue(ClaimTypes.Name) returning null
var userWithoutNameClaim = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]{}, "mock")); // No claims
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = userWithoutNameClaim }
};

// The controller will assign null to networkAssetDto.UserName
// We then make the service throw ArgumentNullException if UserName on the mapped entity is null.
_mockMapper.Setup(m => m.Map<NetworkAsset>(It.IsAny<NetworkAssetDto>()))
.Returns((NetworkAssetDto dto) => new NetworkAsset { ServerName = dto.ServerName, UserName = dto.UserName });

// The controller should throw InvalidOperationException before the service is called when user claim is missing.
// So, the mock setup for _mockNetworkService to throw an exception is not relevant for this specific test path.

// Act
var result = await _controller.Create(networkAssetDto);

// Assert
// Expect the general exception handler to catch the InvalidOperationException and return an Error view.
var viewResult = result.Should().BeOfType<ViewResult>().Subject;
viewResult.Should().NotBeNull(); // Subject can be null if the original result was null, BeOfType would fail first though.
viewResult.ViewName.Should().Be("Error");
viewResult.Model.Should().BeOfType<ErrorViewModel>();

// The service's AddAsync should not be called if the controller throws due to missing user claim.
_mockNetworkService.Verify(s => s.AddAsync(It.IsAny<NetworkAsset>()), Times.Never);
}

[TestMethod]
public async Task Create_Post_WhenServiceThrowsGeneralException_ReturnsErrorView()
{
// Arrange
var networkAssetDto = new NetworkAssetDto { ServerName = "TestServer", Ip = "1.2.3.4" };
var networkAsset = new NetworkAsset { ServerName = "TestServer" }; // UserName will be "testuser" by default setup

_mockMapper.Setup(m => m.Map<NetworkAsset>(networkAssetDto))
.Returns(networkAsset);
_mockNetworkService.Setup(s => s.AddAsync(networkAsset))
.ThrowsAsync(new Exception("Database connection failed")); // Simulate a general exception

// Act
var result = await _controller.Create(networkAssetDto);

// Assert
var viewResult = result.Should().BeOfType<ViewResult>().Subject;
viewResult.ViewName.Should().Be("Error");
viewResult.Model.Should().BeOfType<ErrorViewModel>();
_mockNetworkService.Verify(s => s.AddAsync(networkAsset), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.4" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\NetworkInfrastructure.Web\NetworkInfrastructure.Web.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions tests/NetworkInfrastructure.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace NetworkInfrastructure.Tests;

[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}