using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using JobTrackerApi.Tests.TestSupport; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class JobApplicationsAuthorizationTests { [Fact] public async Task GetHistory_returns_not_found_for_other_users_job() { var dbName = Guid.NewGuid().ToString(); await using var ownerDb = CreateDb(dbName, "owner-1"); var company = new Company { Name = "Acme", OwnerUserId = "owner-1" }; ownerDb.Companies.Add(company); await ownerDb.SaveChangesAsync(); var job = new JobApplication { JobTitle = "Secret Job", CompanyId = company.Id, OwnerUserId = "owner-1" }; ownerDb.JobApplications.Add(job); await ownerDb.SaveChangesAsync(); ownerDb.JobEvents.Add(new JobEvent { JobApplicationId = job.Id, Type = "Created", Note = "owner only" }); await ownerDb.SaveChangesAsync(); await using var attackerDb = CreateDb(dbName, "other-user"); var controller = CreateController(attackerDb); var result = await controller.GetHistory(job.Id, CancellationToken.None); Assert.IsType(result.Result); } [Fact] public async Task GetMatchScore_returns_not_found_for_other_users_job() { var dbName = Guid.NewGuid().ToString(); await using var ownerDb = CreateDb(dbName, "owner-1"); var company = new Company { Name = "Acme", OwnerUserId = "owner-1" }; ownerDb.Companies.Add(company); await ownerDb.SaveChangesAsync(); ownerDb.JobApplications.Add(new JobApplication { JobTitle = "Secret", CompanyId = company.Id, OwnerUserId = "owner-1", Description = "C# .NET" }); await ownerDb.SaveChangesAsync(); var jobId = await ownerDb.JobApplications.Select(j => j.Id).FirstAsync(); await using var attackerDb = CreateDb(dbName, "other-user"); var result = await CreateController(attackerDb).GetMatchScore(jobId, CancellationToken.None); Assert.IsType(result.Result); } [Fact] public async Task GetStatusSuggestion_returns_not_found_for_other_users_job() { var dbName = Guid.NewGuid().ToString(); await using var ownerDb = CreateDb(dbName, "owner-1"); var company = new Company { Name = "Acme", OwnerUserId = "owner-1" }; ownerDb.Companies.Add(company); await ownerDb.SaveChangesAsync(); ownerDb.JobApplications.Add(new JobApplication { JobTitle = "Secret", CompanyId = company.Id, OwnerUserId = "owner-1" }); await ownerDb.SaveChangesAsync(); var jobId = await ownerDb.JobApplications.Select(j => j.Id).FirstAsync(); await using var attackerDb = CreateDb(dbName, "other-user"); var result = await CreateController(attackerDb).GetStatusSuggestion(jobId, CancellationToken.None); Assert.IsType(result.Result); } [Fact] public async Task Job_choices_searches_all_owned_rows_without_leaking_another_tenant() { var dbName = Guid.NewGuid().ToString(); await using (var ownerDb = CreateDb(dbName, "owner-1")) { var company = new Company { Name = "Owner company", OwnerUserId = "owner-1" }; ownerDb.Companies.Add(company); for (var index = 0; index < 130; index++) { ownerDb.JobApplications.Add(new JobApplication { JobTitle = index == 0 ? "Historic target role" : $"Recent role {index}", Company = company, OwnerUserId = "owner-1", SavedAt = DateTime.UtcNow.AddDays(index), }); } await ownerDb.SaveChangesAsync(); } await using (var otherDb = CreateDb(dbName, "owner-2")) { var company = new Company { Name = "Other tenant", OwnerUserId = "owner-2" }; otherDb.JobApplications.Add(new JobApplication { JobTitle = "Historic target private", Company = company, OwnerUserId = "owner-2", }); await otherDb.SaveChangesAsync(); } await using var searchDb = CreateDb(dbName, "owner-1"); var result = await CreateController(searchDb).GetChoices("historic target", 20, default); var choices = Assert.IsType>(Assert.IsType(result.Result).Value); var choice = Assert.Single(choices); Assert.Equal("Historic target role", choice.JobTitle); Assert.Equal("Owner company", choice.CompanyName); } private static JobTrackerContext CreateDb(string dbName, string? userId) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(dbName) .Options; var currentUser = new Mock(); currentUser.SetupGet(service => service.UserId).Returns(userId); return new JobTrackerContext(options, currentUser.Object); } private static JobApplicationsController CreateController(JobTrackerContext db) { var summarizer = new Mock(); var users = TestHostFactory.CreateUserManager(); return new JobApplicationsController(db, summarizer.Object, users.Object) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() } }; } }