diff --git a/net-users-api.tests/Controllers/HomeControllerTests.cs b/net-users-api.tests/Controllers/HomeControllerTests.cs new file mode 100644 index 0000000..d848141 --- /dev/null +++ b/net-users-api.tests/Controllers/HomeControllerTests.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; +using NetUsersApi.Controllers; +using NetUsersApi.Models; + +namespace NetUsersApi.Tests.Controllers; + +public class HomeControllerTests +{ + private readonly Mock> _mockLogger; + private readonly HomeController _controller; + + public HomeControllerTests() + { + _mockLogger = new Mock>(); + _controller = new HomeController(_mockLogger.Object); + } + + [Fact] + public void Index_ReturnsViewResult() + { + // Act + var result = _controller.Index(); + + // Assert + var viewResult = Assert.IsType(result); + Assert.NotNull(viewResult); + } + + [Fact] + public void Index_ReturnsViewResult_WithListOfUsers() + { + // Act + var result = _controller.Index(); + + // Assert + var viewResult = Assert.IsType(result); + var model = Assert.IsAssignableFrom>(viewResult.Model); + Assert.NotEmpty(model); + } + + [Fact] + public void Index_LogsInformation() + { + // Act + _controller.Index(); + + // Assert + _mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString()!.Contains("GET / endpoint called")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } +} diff --git a/net-users-api.tests/Controllers/UsersControllerTests.cs b/net-users-api.tests/Controllers/UsersControllerTests.cs new file mode 100644 index 0000000..2b5645a --- /dev/null +++ b/net-users-api.tests/Controllers/UsersControllerTests.cs @@ -0,0 +1,228 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; +using NetUsersApi.Controllers; +using NetUsersApi.Models; +using System.Reflection; + +namespace NetUsersApi.Tests.Controllers; + +public class UsersControllerTests : IDisposable +{ + private readonly Mock> _mockLogger; + private readonly UsersController _controller; + private readonly List _originalUsers; + + public UsersControllerTests() + { + _mockLogger = new Mock>(); + + // Save the original state and reset to default test data + _originalUsers = GetUsersListViaReflection(); + ResetUsersToDefaultState(); + + _controller = new UsersController(_mockLogger.Object); + } + + public void Dispose() + { + // Reset the static users list after each test + ResetUsersToDefaultState(); + } + + private void ResetUsersToDefaultState() + { + var usersList = GetUsersListViaReflection(); + usersList.Clear(); + usersList.Add(new UserProfile { Id = "1", FullName = "John Doe", Emoji = "๐Ÿ˜€" }); + usersList.Add(new UserProfile { Id = "2", FullName = "Jane Smith", Emoji = "๐Ÿš€" }); + usersList.Add(new UserProfile { Id = "3", FullName = "Robert Johnson", Emoji = "๐ŸŽธ" }); + } + + private List GetUsersListViaReflection() + { + var usersField = typeof(UsersController).GetField("_users", BindingFlags.NonPublic | BindingFlags.Static); + return (List)usersField!.GetValue(null)!; + } + + [Fact] + public void GetUsers_ReturnsOkResult_WithListOfUsers() + { + // Act + var result = _controller.GetUsers(); + + // Assert + var okResult = Assert.IsType(result.Result); + var users = Assert.IsAssignableFrom>(okResult.Value); + Assert.NotEmpty(users); + } + + [Fact] + public void GetUsers_LogsInformation() + { + // Act + _controller.GetUsers(); + + // Assert + _mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString()!.Contains("GET /api/v1/users endpoint called")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void GetUser_WithValidId_ReturnsOkResult_WithUser() + { + // Arrange + var validId = "1"; + + // Act + var result = _controller.GetUser(validId); + + // Assert + var okResult = Assert.IsType(result.Result); + var user = Assert.IsType(okResult.Value); + Assert.Equal(validId, user.Id); + Assert.Equal("John Doe", user.FullName); + } + + [Fact] + public void GetUser_WithInvalidId_ReturnsNotFound() + { + // Arrange + var invalidId = "999"; + + // Act + var result = _controller.GetUser(invalidId); + + // Assert + var notFoundResult = Assert.IsType(result.Result); + Assert.NotNull(notFoundResult.Value); + } + + [Fact] + public void CreateUser_WithValidUser_ReturnsCreatedAtAction() + { + // Arrange + var newUser = new UserProfile + { + Id = "100", + FullName = "Test User", + Emoji = "๐Ÿงช" + }; + + // Act + var result = _controller.CreateUser(newUser); + + // Assert + var createdResult = Assert.IsType(result.Result); + var returnedUser = Assert.IsType(createdResult.Value); + Assert.Equal(newUser.Id, returnedUser.Id); + Assert.Equal(newUser.FullName, returnedUser.FullName); + Assert.Equal(newUser.Emoji, returnedUser.Emoji); + } + + [Fact] + public void CreateUser_WithNullUser_ReturnsBadRequest() + { + // Act + var result = _controller.CreateUser(null!); + + // Assert + var badRequestResult = Assert.IsType(result.Result); + Assert.NotNull(badRequestResult.Value); + } + + [Fact] + public void UpdateUser_WithValidIdAndUser_ReturnsOkResult() + { + // Arrange + var userId = "1"; + var updatedUser = new UserProfile + { + Id = "1", + FullName = "John Updated", + Emoji = "๐Ÿ˜Ž" + }; + + // Act + var result = _controller.UpdateUser(userId, updatedUser); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedUser = Assert.IsType(okResult.Value); + Assert.Equal(userId, returnedUser.Id); + Assert.Equal(updatedUser.FullName, returnedUser.FullName); + Assert.Equal(updatedUser.Emoji, returnedUser.Emoji); + } + + [Fact] + public void UpdateUser_WithInvalidId_ReturnsNotFound() + { + // Arrange + var invalidId = "999"; + var updatedUser = new UserProfile + { + Id = "999", + FullName = "Non-existent User", + Emoji = "โŒ" + }; + + // Act + var result = _controller.UpdateUser(invalidId, updatedUser); + + // Assert + var notFoundResult = Assert.IsType(result.Result); + Assert.NotNull(notFoundResult.Value); + } + + [Fact] + public void UpdateUser_WithNullUser_ReturnsBadRequest() + { + // Arrange + var userId = "1"; + + // Act + var result = _controller.UpdateUser(userId, null!); + + // Assert + var badRequestResult = Assert.IsType(result.Result); + Assert.NotNull(badRequestResult.Value); + } + + [Fact] + public void UpdateUser_EnsuresIdDoesNotChange() + { + // Arrange + var originalId = "1"; + var updatedUser = new UserProfile + { + Id = "different-id", + FullName = "John Changed", + Emoji = "๐Ÿ”„" + }; + + // Act + var result = _controller.UpdateUser(originalId, updatedUser); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedUser = Assert.IsType(okResult.Value); + Assert.Equal(originalId, returnedUser.Id); + Assert.NotEqual("different-id", returnedUser.Id); + } + + [Fact] + public void DeleteUser_ThrowsNotImplementedException() + { + // Arrange + var userId = "1"; + + // Act & Assert + Assert.Throws(() => _controller.DeleteUser(userId)); + } +} diff --git a/net-users-api.tests/Models/UserProfileTests.cs b/net-users-api.tests/Models/UserProfileTests.cs new file mode 100644 index 0000000..16ea644 --- /dev/null +++ b/net-users-api.tests/Models/UserProfileTests.cs @@ -0,0 +1,67 @@ +using NetUsersApi.Models; + +namespace NetUsersApi.Tests.Models; + +public class UserProfileTests +{ + [Fact] + public void UserProfile_CanBeCreated_WithRequiredProperties() + { + // Arrange & Act + var userProfile = new UserProfile + { + Id = "1", + FullName = "John Doe", + Emoji = "๐Ÿ˜€" + }; + + // Assert + Assert.NotNull(userProfile); + Assert.Equal("1", userProfile.Id); + Assert.Equal("John Doe", userProfile.FullName); + Assert.Equal("๐Ÿ˜€", userProfile.Emoji); + } + + [Fact] + public void UserProfile_PropertiesCanBeSet() + { + // Arrange + var userProfile = new UserProfile + { + Id = "initial", + FullName = "Initial Name", + Emoji = "๐Ÿ‘ค" + }; + + // Act + userProfile.Id = "updated"; + userProfile.FullName = "Updated Name"; + userProfile.Emoji = "โœจ"; + + // Assert + Assert.Equal("updated", userProfile.Id); + Assert.Equal("Updated Name", userProfile.FullName); + Assert.Equal("โœจ", userProfile.Emoji); + } + + [Fact] + public void UserProfile_AllowsEmojisInEmojiFiled() + { + // Arrange + var emojis = new[] { "๐Ÿ˜€", "๐Ÿš€", "๐ŸŽธ", "๐Ÿงช", "โœ…", "โŒ" }; + + foreach (var emoji in emojis) + { + // Act + var userProfile = new UserProfile + { + Id = "test", + FullName = "Test User", + Emoji = emoji + }; + + // Assert + Assert.Equal(emoji, userProfile.Emoji); + } + } +} diff --git a/net-users-api.tests/net-users-api.tests.csproj b/net-users-api.tests/net-users-api.tests.csproj new file mode 100644 index 0000000..ca652b4 --- /dev/null +++ b/net-users-api.tests/net-users-api.tests.csproj @@ -0,0 +1,27 @@ +๏ปฟ + + + net9.0 + net_users_api.tests + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net-users-demo.sln b/net-users-demo.sln index e9bb8f5..2dc8c28 100644 --- a/net-users-demo.sln +++ b/net-users-demo.sln @@ -1,22 +1,48 @@ -๏ปฟ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "net-users-api", "net-users-api\net-users-api.csproj", "{F2C0F837-02EA-42E5-BDC0-48C31DD4245D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal +๏ปฟ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "net-users-api", "net-users-api\net-users-api.csproj", "{F2C0F837-02EA-42E5-BDC0-48C31DD4245D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "net-users-api.tests", "net-users-api.tests\net-users-api.tests.csproj", "{B94E659F-D364-46C2-8715-64A47A27F916}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|x64.Build.0 = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Debug|x86.Build.0 = Debug|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|Any CPU.Build.0 = Release|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|x64.ActiveCfg = Release|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|x64.Build.0 = Release|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|x86.ActiveCfg = Release|Any CPU + {F2C0F837-02EA-42E5-BDC0-48C31DD4245D}.Release|x86.Build.0 = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|x64.ActiveCfg = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|x64.Build.0 = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|x86.ActiveCfg = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Debug|x86.Build.0 = Debug|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|Any CPU.Build.0 = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|x64.ActiveCfg = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|x64.Build.0 = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|x86.ActiveCfg = Release|Any CPU + {B94E659F-D364-46C2-8715-64A47A27F916}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal