14b396a452
Add owner-filtered, job-cascading private draft storage with reversible SQLite and MariaDB migration paths. No API or UI exposes draft content yet.
78 lines
3.2 KiB
C#
78 lines
3.2 KiB
C#
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace JobTrackerApi.Tests;
|
|
|
|
public sealed class EmailDraftPersistenceTests
|
|
{
|
|
[Fact]
|
|
public async Task Drafts_are_owner_filtered_and_follow_the_owned_job_lifecycle()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
var options = new DbContextOptionsBuilder<JobTrackerContext>().UseSqlite(connection).Options;
|
|
|
|
int userOneJobId;
|
|
int userTwoJobId;
|
|
await using (var seed = Context(options, null))
|
|
{
|
|
await seed.Database.EnsureCreatedAsync();
|
|
var companyOne = new Company { Name = "One", OwnerUserId = "user-1" };
|
|
var companyTwo = new Company { Name = "Two", OwnerUserId = "user-2" };
|
|
seed.Companies.AddRange(companyOne, companyTwo);
|
|
await seed.SaveChangesAsync();
|
|
var jobOne = new JobApplication { JobTitle = "Role one", CompanyId = companyOne.Id, OwnerUserId = "user-1" };
|
|
var jobTwo = new JobApplication { JobTitle = "Role two", CompanyId = companyTwo.Id, OwnerUserId = "user-2" };
|
|
seed.JobApplications.AddRange(jobOne, jobTwo);
|
|
await seed.SaveChangesAsync();
|
|
userOneJobId = jobOne.Id;
|
|
userTwoJobId = jobTwo.Id;
|
|
seed.EmailDrafts.AddRange(
|
|
Draft("user-1", jobOne.Id, "one@example.test"),
|
|
Draft("user-2", jobTwo.Id, "two@example.test"));
|
|
await seed.SaveChangesAsync();
|
|
}
|
|
|
|
await using (var userOne = Context(options, "user-1"))
|
|
{
|
|
var visible = Assert.Single(await userOne.EmailDrafts.AsNoTracking().ToListAsync());
|
|
Assert.Equal(userOneJobId, visible.JobApplicationId);
|
|
Assert.Equal("one@example.test", visible.To);
|
|
var job = await userOne.JobApplications.SingleAsync(item => item.Id == userOneJobId);
|
|
userOne.JobApplications.Remove(job);
|
|
await userOne.SaveChangesAsync();
|
|
}
|
|
|
|
await using var verify = Context(options, null);
|
|
var remaining = Assert.Single(await verify.EmailDrafts.IgnoreQueryFilters().AsNoTracking().ToListAsync());
|
|
Assert.Equal("user-2", remaining.OwnerUserId);
|
|
Assert.Equal(userTwoJobId, remaining.JobApplicationId);
|
|
}
|
|
|
|
private static EmailDraft Draft(string ownerUserId, int jobApplicationId, string recipient) => new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
OwnerUserId = ownerUserId,
|
|
JobApplicationId = jobApplicationId,
|
|
Provider = "gmail",
|
|
To = recipient,
|
|
Subject = "Synthetic draft",
|
|
BodyText = "Synthetic private draft body.",
|
|
ThreadId = "thread-1",
|
|
CreatedAtUtc = DateTime.UtcNow,
|
|
UpdatedAtUtc = DateTime.UtcNow,
|
|
};
|
|
|
|
private static JobTrackerContext Context(DbContextOptions<JobTrackerContext> options, string? userId)
|
|
{
|
|
var currentUser = new Mock<ICurrentUserService>();
|
|
currentUser.SetupGet(service => service.UserId).Returns(userId);
|
|
return new JobTrackerContext(options, currentUser.Object);
|
|
}
|
|
}
|