a08ba9b45d
Replace ineffective pageSize=100 selectors with a bounded owner-filtered search endpoint for composing and moving linked threads.
139 lines
5.6 KiB
C#
139 lines
5.6 KiB
C#
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<NotFoundResult>(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<NotFoundResult>(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<NotFoundResult>(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<List<JobApplicationChoiceDto>>(Assert.IsType<OkObjectResult>(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<JobTrackerContext>()
|
|
.UseInMemoryDatabase(dbName)
|
|
.Options;
|
|
var currentUser = new Mock<ICurrentUserService>();
|
|
currentUser.SetupGet(service => service.UserId).Returns(userId);
|
|
return new JobTrackerContext(options, currentUser.Object);
|
|
}
|
|
|
|
private static JobApplicationsController CreateController(JobTrackerContext db)
|
|
{
|
|
var summarizer = new Mock<ISummarizerService>();
|
|
var users = TestHostFactory.CreateUserManager();
|
|
return new JobApplicationsController(db, summarizer.Object, users.Object)
|
|
{
|
|
ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext()
|
|
}
|
|
};
|
|
}
|
|
}
|