Skip to content
Draft
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
119 changes: 119 additions & 0 deletions Controllers/DashboardController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using Microsoft.AspNetCore.Mvc;

namespace WebApp_AppService.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class DashboardController : ControllerBase
{
/// <summary>
/// Extracts a critical segment from User-Agent string with proper bounds checking
/// to prevent IndexOutOfRangeException
/// </summary>
/// <param name="userAgent">The User-Agent string to parse</param>
/// <returns>Critical segment or a safe default value</returns>
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
var criticalSegment = segments[0];

// 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 (ArgumentOutOfRangeException)
{
// 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)
{
// 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";
}
}

/// <summary>
/// Admin dashboard endpoint that processes User-Agent information
/// </summary>
/// <returns>Dashboard information including parsed User-Agent segment</returns>
[HttpGet]
[Route("admin")]
public ActionResult<object> 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)
{
// 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
{
Status = "Error",
Message = "Failed to load admin dashboard"
});
}
}

/// <summary>
/// Health check endpoint
/// </summary>
[HttpGet]
[Route("health")]
public ActionResult<object> GetHealth()
{
return Ok(new
{
Status = "Healthy",
Timestamp = DateTime.UtcNow
});
}
}
}
251 changes: 251 additions & 0 deletions WebApp_AppService.Tests/DashboardControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using WebApp_AppService.Controllers;
using Xunit;

namespace WebApp_AppService.Tests
{
/// <summary>
/// Unit tests for DashboardController focusing on User-Agent parsing
/// and boundary conditions to prevent IndexOutOfRangeException
/// </summary>
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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(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<OkObjectResult>(result.Result);
Assert.NotNull(okResult.Value);
}
}
}
Loading