using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace JobTrackerApi.Tests; // Regression guard for the production tenant-read failure: the "local" JwtBearer OnTokenValidated // resolves the request-scoped JobTrackerContext to run session validation BEFORE the auth middleware // sets HttpContext.User. When CurrentUserId was captured in the DbContext constructor, that froze it // to null for the whole request, and every global-query-filtered read returned WHERE FALSE — created // rows 404'd on read and lists came back empty, while writes (which set OwnerUserId from the // controller-resolved user) still succeeded. // // The fix makes CurrentUserId read ICurrentUserService.UserId live on each access. These tests fail // if it reverts to a constructor snapshot. public sealed class CurrentUserIdLiveEvaluationTests { private static (JobTrackerContext db, Mock user) New(string? initialUserId) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options; var user = new Mock(); user.SetupGet(s => s.UserId).Returns(initialUserId); return (new JobTrackerContext(options, user.Object), user); } [Fact] public void CurrentUserId_reflects_the_user_resolved_after_construction() { // Constructed while unauthenticated (UserId null), exactly as OnTokenValidated does. var (db, user) = New(initialUserId: null); Assert.Null(db.CurrentUserId); // The auth middleware then assigns the principal; UserId becomes non-null on the SAME instance. user.SetupGet(s => s.UserId).Returns("user-1"); Assert.Equal("user-1", db.CurrentUserId); } [Fact] public async Task A_row_created_while_unauthenticated_context_is_readable_once_the_user_resolves() { var (db, user) = New(initialUserId: null); // Write happens with an explicit owner (mirrors CreateAsync taking the controller-resolved id), // while the context was built before the user resolved. db.CvVariants.Add(new CvVariant { OwnerUserId = "user-1", Name = "Test", PublicSlug = "slug-1", SettingsJson = "{}", Version = 1, }); await db.SaveChangesAsync(); // Before the user resolves, the tenant filter denies (deny-on-null preserved). Assert.Empty(await db.CvVariants.ToListAsync()); // Once the middleware sets the user, the same context reads the row — not WHERE FALSE. user.SetupGet(s => s.UserId).Returns("user-1"); var visible = await db.CvVariants.ToListAsync(); Assert.Single(visible); Assert.Equal("user-1", visible[0].OwnerUserId); } }