diff --git a/JobTrackerApi.Tests/AuthAndSystemControllerTests.cs b/JobTrackerApi.Tests/AuthAndSystemControllerTests.cs new file mode 100644 index 0000000..dc97dc8 --- /dev/null +++ b/JobTrackerApi.Tests/AuthAndSystemControllerTests.cs @@ -0,0 +1,121 @@ +using JobTrackerApi.Controllers; +using JobTrackerApi.Models; +using JobTrackerApi.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace JobTrackerApi.Tests; + +public sealed class AuthAndSystemControllerTests +{ + [Fact] + public async Task Update_profile_applies_trimmed_profile_fields() + { + var user = new ApplicationUser { Email = "old@example.com", UserName = "olduser" }; + var userManager = CreateUserManager(); + userManager.Setup(x => x.GetUserAsync(It.IsAny())).ReturnsAsync(user); + userManager.Setup(x => x.UpdateAsync(user)).ReturnsAsync(IdentityResult.Success); + + var controller = new AuthController(BuildConfig(), userManager.Object, Mock.Of(), Mock.Of(), Mock.Of()); + + var result = await controller.UpdateProfile(new AuthController.UpdateProfileRequest(" new@example.com ", " newuser ", " Ada ", " Lovelace ", " Ada L. ")); + + Assert.IsType(result); + Assert.Equal("new@example.com", user.Email); + Assert.Equal("newuser", user.UserName); + Assert.Equal("Ada", user.FirstName); + Assert.Equal("Lovelace", user.LastName); + Assert.Equal("Ada L.", user.DisplayName); + } + + [Fact] + public void Me_result_includes_google_link_details_for_local_users() + { + var method = typeof(AuthController).GetMethod("ToMeResult", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + Assert.NotNull(method); + + var user = new ApplicationUser + { + Id = "user-1", + Email = "person@example.com", + UserName = "person", + FirstName = "Ada", + LastName = "Lovelace", + DisplayName = "Ada Lovelace", + GoogleSubject = "sub-123", + GoogleEmail = "person@example.com", + GoogleLinkedAt = DateTimeOffset.UtcNow, + }; + + var result = (AuthController.MeResult)method!.Invoke(null, new object[] { user, new List { "Admin" } })!; + + Assert.Equal("local", result.Provider); + Assert.Equal("Ada", result.FirstName); + Assert.Equal("Lovelace", result.LastName); + Assert.Equal("Ada Lovelace", result.DisplayName); + Assert.NotNull(result.GoogleLink); + Assert.True(result.GoogleLink!.Linked); + Assert.Equal("person@example.com", result.GoogleLink.Email); + } + + [Fact] + public async Task Admin_system_probe_endpoint_runs_probe_once() + { + var summarizer = new Mock(); + summarizer.Setup(x => x.RunProbeAsync(It.IsAny())).Returns(Task.CompletedTask); + + var controller = new AdminSystemController(BuildConfig(), new AppPaths(BuildConfig(), new FakeHostEnv()), null!, summarizer.Object, new FakeEnv()); + + var result = await controller.RunSummarizerProbe(CancellationToken.None); + + Assert.IsType(result); + summarizer.Verify(x => x.RunProbeAsync(It.IsAny()), Times.Once); + } + + private static IConfiguration BuildConfig() + { + return new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary()) + .Build(); + } + + private static Mock> CreateUserManager() + { + var store = new Mock>(); + return new Mock>( + store.Object, + Options.Create(new IdentityOptions()), + new PasswordHasher(), + Array.Empty>(), + Array.Empty>(), + new UpperInvariantLookupNormalizer(), + new IdentityErrorDescriber(), + null!, + new NullLogger>() + ); + } + + private sealed class FakeHostEnv : Microsoft.Extensions.Hosting.IHostEnvironment + { + public string EnvironmentName { get; set; } = "Test"; + public string ApplicationName { get; set; } = "JobTrackerApi"; + public string ContentRootPath { get; set; } = AppContext.BaseDirectory; + public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!; + } + + private sealed class FakeEnv : Microsoft.AspNetCore.Hosting.IWebHostEnvironment + { + public string ApplicationName { get; set; } = "JobTrackerApi"; + public Microsoft.Extensions.FileProviders.IFileProvider WebRootFileProvider { get; set; } = null!; + public string WebRootPath { get; set; } = string.Empty; + public string EnvironmentName { get; set; } = "Test"; + public string ContentRootPath { get; set; } = AppContext.BaseDirectory; + public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!; + } +} diff --git a/JobTrackerApi.Tests/JobTrackerApi.Tests.csproj b/JobTrackerApi.Tests/JobTrackerApi.Tests.csproj index ea1de27..fe6c767 100644 --- a/JobTrackerApi.Tests/JobTrackerApi.Tests.csproj +++ b/JobTrackerApi.Tests/JobTrackerApi.Tests.csproj @@ -13,6 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/job-tracker-ui/src/attachments.test.tsx b/job-tracker-ui/src/attachments.test.tsx new file mode 100644 index 0000000..d955239 --- /dev/null +++ b/job-tracker-ui/src/attachments.test.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { ConfirmProvider } from './confirm'; +import { PromptProvider } from './prompt'; +import Attachments from './components/Attachments'; +import { ToastProvider } from './toast'; +import { api } from './api'; + +jest.mock('./api', () => ({ + api: { + get: jest.fn(), + post: jest.fn(), + patch: jest.fn(), + delete: jest.fn(), + }, +})); + +const mockedApi = api as jest.Mocked; + +test('attachments empty state renders drag and drop guidance', async () => { + mockedApi.get.mockResolvedValueOnce({ data: [] } as any); + + render( + + + + + + + , + ); + + expect(await screen.findByText(/drag and drop files here/i)).toBeInTheDocument(); + expect(screen.getByText(/no attachments yet/i)).toBeInTheDocument(); + expect(screen.getByText(/max 10 mb each/i)).toBeInTheDocument(); +});