refactor, security updates, cv extraction upgrades
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using JobTrackerApi.Controllers;
|
||||
using JobTrackerApi.Models;
|
||||
using JobTrackerApi.Services;
|
||||
using JobTrackerApi.Tests.TestSupport;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace JobTrackerApi.Tests;
|
||||
|
||||
public sealed class ClientErrorsControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Report_logs_sanitized_payload_instead_of_raw_stacks()
|
||||
{
|
||||
var logger = new ListLogger<ClientErrorsController>();
|
||||
var controller = new ClientErrorsController(logger);
|
||||
var stack = "TypeError: bad\n at render(App.tsx:10)\nextra-secret-line";
|
||||
var componentStack = "at Widget\n at Dashboard";
|
||||
|
||||
var result = controller.Report(new ClientErrorsController.ClientErrorReport(
|
||||
ErrorId: " err-1 ",
|
||||
Message: " boom ",
|
||||
Stack: stack,
|
||||
ComponentStack: componentStack,
|
||||
Url: " https://jobtracker.test/jobs ",
|
||||
UserAgent: " Browser\nAgent ",
|
||||
At: " 2026-04-10T18:00:00Z "));
|
||||
|
||||
Assert.IsType<NoContentResult>(result);
|
||||
var entry = Assert.Single(logger.Entries);
|
||||
Assert.Contains("stackHash=", entry.Message);
|
||||
Assert.Contains("componentHash=", entry.Message);
|
||||
Assert.Contains("TypeError: bad | at render(App.tsx:10)", entry.Message);
|
||||
Assert.DoesNotContain(stack, entry.Message);
|
||||
Assert.DoesNotContain(componentStack, entry.Message);
|
||||
Assert.DoesNotContain("extra-secret-line", entry.Message);
|
||||
Assert.DoesNotContain("Browser\nAgent", entry.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Upload_avatar_rejects_file_when_extension_or_detected_bytes_are_not_supported()
|
||||
{
|
||||
var user = new ApplicationUser { Id = "user-1", Email = "person@example.com", UserName = "person@example.com" };
|
||||
var userManager = TestHostFactory.CreateUserManager();
|
||||
userManager.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);
|
||||
|
||||
var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>(), Mock.Of<ILogger<AuthController>>())
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "user-1") }, "local")) }
|
||||
}
|
||||
};
|
||||
|
||||
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes("not really a png"));
|
||||
IFormFile file = new FormFile(stream, 0, stream.Length, "file", "avatar.png")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "image/png"
|
||||
};
|
||||
|
||||
var result = await controller.UploadAvatar(file);
|
||||
|
||||
var badRequest = Assert.IsType<BadRequestObjectResult>(result);
|
||||
Assert.Equal("Only PNG, JPEG, or WebP images are supported.", badRequest.Value);
|
||||
userManager.Verify(x => x.UpdateAsync(It.IsAny<ApplicationUser>()), Times.Never);
|
||||
}
|
||||
|
||||
private static IConfiguration BuildConfig()
|
||||
{
|
||||
return new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
}
|
||||
|
||||
private sealed class ListLogger<T> : ILogger<T>
|
||||
{
|
||||
public List<LogEntry> Entries { get; } = new();
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
Entries.Add(new LogEntry(logLevel, formatter(state, exception)));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record LogEntry(LogLevel Level, string Message);
|
||||
|
||||
private sealed class NullScope : IDisposable
|
||||
{
|
||||
public static NullScope Instance { get; } = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user