From b4efc66e2854c100780174af5689df3f2073fbda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:29:17 +0000 Subject: [PATCH 1/5] Initial plan From c3bb94c95e8d2d70fc567d850a4ca6ca73011cd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:34:22 +0000 Subject: [PATCH 2/5] Add DashboardController with robust User-Agent parsing and comprehensive tests Co-authored-by: mrsharm <68247673+mrsharm@users.noreply.github.com> --- Controllers/DashboardController.cs | 111 ++++++++ .../DashboardControllerTests.cs | 251 ++++++++++++++++++ .../WebApp_AppService.Tests.csproj | 28 ++ WebApp_AppService.csproj | 6 + WebApp_AppService.sln | 28 +- 5 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 Controllers/DashboardController.cs create mode 100644 WebApp_AppService.Tests/DashboardControllerTests.cs create mode 100644 WebApp_AppService.Tests/WebApp_AppService.Tests.csproj diff --git a/Controllers/DashboardController.cs b/Controllers/DashboardController.cs new file mode 100644 index 0000000..d660bb6 --- /dev/null +++ b/Controllers/DashboardController.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApp_AppService.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class DashboardController : ControllerBase + { + /// + /// Extracts a critical segment from User-Agent string with proper bounds checking + /// to prevent IndexOutOfRangeException + /// + /// The User-Agent string to parse + /// Critical segment or a safe default value + private string ExtractCriticalSegment(string? userAgent) + { + // Defensive check: Handle null or empty User-Agent + if (string.IsNullOrWhiteSpace(userAgent)) + { + return "Unknown"; + } + + try + { + // Split by common delimiters in User-Agent strings + var segments = userAgent.Split(new[] { ' ', '/', '(', ')' }, StringSplitOptions.RemoveEmptyEntries); + + // Bounds check: Ensure we have at least one segment + if (segments.Length == 0) + { + return "Unknown"; + } + + // Safely access the first segment + // In production, this might target a specific index, but with bounds checking + var criticalSegment = segments.Length > 0 ? segments[0] : "Unknown"; + + // Additional validation: Ensure the segment is not empty after trim + criticalSegment = criticalSegment.Trim(); + if (string.IsNullOrEmpty(criticalSegment)) + { + return "Unknown"; + } + + // Limit the length to prevent potential buffer issues + const int maxLength = 100; + if (criticalSegment.Length > maxLength) + { + criticalSegment = criticalSegment.Substring(0, maxLength); + } + + return criticalSegment; + } + catch (Exception) + { + // Catch any unexpected exceptions during parsing + return "Unknown"; + } + } + + /// + /// Admin dashboard endpoint that processes User-Agent information + /// + /// Dashboard information including parsed User-Agent segment + [HttpGet] + [Route("admin")] + public ActionResult GetAdminDashboard() + { + try + { + // Safely retrieve User-Agent from request headers + var userAgent = Request.Headers["User-Agent"].ToString(); + + // Extract critical segment with bounds checking + var criticalSegment = ExtractCriticalSegment(userAgent); + + return Ok(new + { + Status = "Success", + Message = "Admin dashboard loaded successfully", + UserAgentSegment = criticalSegment, + Timestamp = DateTime.UtcNow + }); + } + catch (Exception ex) + { + // Log the exception (in production, use proper logging) + return StatusCode(500, new + { + Status = "Error", + Message = "Failed to load admin dashboard", + Error = ex.Message + }); + } + } + + /// + /// Health check endpoint + /// + [HttpGet] + [Route("health")] + public ActionResult GetHealth() + { + return Ok(new + { + Status = "Healthy", + Timestamp = DateTime.UtcNow + }); + } + } +} diff --git a/WebApp_AppService.Tests/DashboardControllerTests.cs b/WebApp_AppService.Tests/DashboardControllerTests.cs new file mode 100644 index 0000000..6fdbf89 --- /dev/null +++ b/WebApp_AppService.Tests/DashboardControllerTests.cs @@ -0,0 +1,251 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Moq; +using WebApp_AppService.Controllers; +using Xunit; + +namespace WebApp_AppService.Tests +{ + /// + /// Unit tests for DashboardController focusing on User-Agent parsing + /// and boundary conditions to prevent IndexOutOfRangeException + /// + public class DashboardControllerTests + { + private DashboardController CreateControllerWithUserAgent(string? userAgent) + { + var controller = new DashboardController(); + var httpContext = new DefaultHttpContext(); + + if (userAgent != null) + { + httpContext.Request.Headers["User-Agent"] = userAgent; + } + + controller.ControllerContext = new ControllerContext + { + HttpContext = httpContext + }; + + return controller; + } + + [Fact] + public void GetAdminDashboard_WithValidUserAgent_ReturnsSuccess() + { + // Arrange + var controller = CreateControllerWithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + + var response = okResult.Value; + var statusProperty = response.GetType().GetProperty("Status"); + Assert.NotNull(statusProperty); + Assert.Equal("Success", statusProperty.GetValue(response)); + } + + [Fact] + public void GetAdminDashboard_WithNullUserAgent_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent(null); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithEmptyUserAgent_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent(""); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithWhitespaceUserAgent_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent(" "); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithShortUserAgent_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent("A"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithMalformedUserAgent_DoesNotThrowException() + { + // Arrange - User-Agent with only special characters + var controller = CreateControllerWithUserAgent("///()()()"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithCorruptedUserAgent_DoesNotThrowException() + { + // Arrange - Simulating corrupted/unexpected format + var controller = CreateControllerWithUserAgent("@#$%^&*()"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithVeryLongUserAgent_DoesNotThrowException() + { + // Arrange - Very long User-Agent string + var longUserAgent = new string('A', 10000); + var controller = CreateControllerWithUserAgent(longUserAgent); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithMultipleSlashes_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent("///"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithOnlyParentheses_DoesNotThrowException() + { + // Arrange + var controller = CreateControllerWithUserAgent("()()()"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithCommonBrowserUserAgents_ReturnsSuccess() + { + // Arrange - Test with multiple common browser User-Agents + var userAgents = new[] + { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1" + }; + + foreach (var userAgent in userAgents) + { + var controller = CreateControllerWithUserAgent(userAgent); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + } + + [Fact] + public void GetHealth_ReturnsHealthy() + { + // Arrange + var controller = new DashboardController(); + + // Act + var result = controller.GetHealth(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + + var response = okResult.Value; + var statusProperty = response.GetType().GetProperty("Status"); + Assert.NotNull(statusProperty); + Assert.Equal("Healthy", statusProperty.GetValue(response)); + } + + [Fact] + public void GetAdminDashboard_WithBotUserAgent_DoesNotThrowException() + { + // Arrange - Bot/crawler User-Agent + var controller = CreateControllerWithUserAgent("Googlebot/2.1 (+http://www.google.com/bot.html)"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + + [Fact] + public void GetAdminDashboard_WithMinimalUserAgent_DoesNotThrowException() + { + // Arrange - Minimal valid User-Agent + var controller = CreateControllerWithUserAgent("curl/7.68.0"); + + // Act + var result = controller.GetAdminDashboard(); + + // Assert + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + } + } +} diff --git a/WebApp_AppService.Tests/WebApp_AppService.Tests.csproj b/WebApp_AppService.Tests/WebApp_AppService.Tests.csproj new file mode 100644 index 0000000..ca0c8a5 --- /dev/null +++ b/WebApp_AppService.Tests/WebApp_AppService.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApp_AppService.csproj b/WebApp_AppService.csproj index 1b9714c..2f9e161 100644 --- a/WebApp_AppService.csproj +++ b/WebApp_AppService.csproj @@ -6,6 +6,12 @@ enable + + + + + + diff --git a/WebApp_AppService.sln b/WebApp_AppService.sln index e08cd3a..89279e6 100644 --- a/WebApp_AppService.sln +++ b/WebApp_AppService.sln @@ -1,20 +1,46 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.13.35913.81 d17.13 +VisualStudioVersion = 17.13.35913.81 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp_AppService", "WebApp_AppService.csproj", "{BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp_AppService.Tests", "WebApp_AppService.Tests\WebApp_AppService.Tests.csproj", "{AE8E4E73-82A1-48C5-945B-97C3D5B909CF}" +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 {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|x64.Build.0 = Debug|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Debug|x86.Build.0 = Debug|Any CPU {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|Any CPU.Build.0 = Release|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|x64.ActiveCfg = Release|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|x64.Build.0 = Release|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|x86.ActiveCfg = Release|Any CPU + {BDFF82FF-2E1C-499D-BAA2-AB3D03F3C9CD}.Release|x86.Build.0 = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|x64.Build.0 = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Debug|x86.Build.0 = Debug|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|Any CPU.Build.0 = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|x64.ActiveCfg = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|x64.Build.0 = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|x86.ActiveCfg = Release|Any CPU + {AE8E4E73-82A1-48C5-945B-97C3D5B909CF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From f5bc7dd14e4111c9601768a1d8efeb124dd2ec28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:36:45 +0000 Subject: [PATCH 3/5] Address code review feedback: improve exception handling and remove redundant check Co-authored-by: mrsharm <68247673+mrsharm@users.noreply.github.com> --- Controllers/DashboardController.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Controllers/DashboardController.cs b/Controllers/DashboardController.cs index d660bb6..797aec7 100644 --- a/Controllers/DashboardController.cs +++ b/Controllers/DashboardController.cs @@ -32,8 +32,7 @@ private string ExtractCriticalSegment(string? userAgent) } // Safely access the first segment - // In production, this might target a specific index, but with bounds checking - var criticalSegment = segments.Length > 0 ? segments[0] : "Unknown"; + var criticalSegment = segments[0]; // Additional validation: Ensure the segment is not empty after trim criticalSegment = criticalSegment.Trim(); @@ -51,9 +50,14 @@ private string ExtractCriticalSegment(string? userAgent) return criticalSegment; } - catch (Exception) + catch (ArgumentException) { - // Catch any unexpected exceptions during parsing + // Handle specific exceptions that could occur during string operations + return "Unknown"; + } + catch (IndexOutOfRangeException) + { + // Handle any unexpected index access issues return "Unknown"; } } @@ -82,14 +86,14 @@ public ActionResult GetAdminDashboard() Timestamp = DateTime.UtcNow }); } - catch (Exception ex) + catch (Exception) { - // Log the exception (in production, use proper logging) + // In production, log the exception details server-side + // Do not expose internal exception details to clients return StatusCode(500, new { Status = "Error", - Message = "Failed to load admin dashboard", - Error = ex.Message + Message = "Failed to load admin dashboard" }); } } From 1cf2bff42be382438845a4e66bf73b9e031b14f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:37:53 +0000 Subject: [PATCH 4/5] Fix exception handling: catch ArgumentOutOfRangeException and improve comments Co-authored-by: mrsharm <68247673+mrsharm@users.noreply.github.com> --- Controllers/DashboardController.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Controllers/DashboardController.cs b/Controllers/DashboardController.cs index 797aec7..319053f 100644 --- a/Controllers/DashboardController.cs +++ b/Controllers/DashboardController.cs @@ -50,14 +50,14 @@ private string ExtractCriticalSegment(string? userAgent) return criticalSegment; } - catch (ArgumentException) + catch (ArgumentOutOfRangeException) { - // Handle specific exceptions that could occur during string operations + // Handle substring operations that may exceed bounds return "Unknown"; } - catch (IndexOutOfRangeException) + catch (ArgumentException) { - // Handle any unexpected index access issues + // Handle specific exceptions that could occur during string operations return "Unknown"; } } @@ -88,6 +88,8 @@ public ActionResult GetAdminDashboard() } catch (Exception) { + // Safety net for any unexpected exceptions during request processing + // ExtractCriticalSegment has its own specific exception handling // In production, log the exception details server-side // Do not expose internal exception details to clients return StatusCode(500, new From a0e5798173edb86db959df9542ee914a8db17e44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:38:46 +0000 Subject: [PATCH 5/5] Add clarifying comments for defensive exception handlers Co-authored-by: mrsharm <68247673+mrsharm@users.noreply.github.com> --- Controllers/DashboardController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Controllers/DashboardController.cs b/Controllers/DashboardController.cs index 319053f..009ec34 100644 --- a/Controllers/DashboardController.cs +++ b/Controllers/DashboardController.cs @@ -52,12 +52,14 @@ private string ExtractCriticalSegment(string? userAgent) } catch (ArgumentOutOfRangeException) { - // Handle substring operations that may exceed bounds + // Defensive: Handle substring operations that may exceed bounds + // This should not occur with current bounds checking but protects against future code changes return "Unknown"; } catch (ArgumentException) { - // Handle specific exceptions that could occur during string operations + // Defensive: Handle string operations that may fail with invalid arguments + // This should not occur with current validation but protects against future code changes return "Unknown"; } }