170 lines
10 KiB
C#
170 lines
10 KiB
C#
using System.Security.Claims;
|
|
using JobTrackerApi.Controllers;
|
|
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using JobTrackerApi.Tests.TestSupport;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace JobTrackerApi.Tests;
|
|
|
|
public sealed partial class CoverLetterWriterTests
|
|
{
|
|
[Fact]
|
|
public async Task Document_settings_only_saves_advance_shared_revision_preserve_omission_and_restore_old_defaults()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
await using var db = Context(connection);
|
|
var job = await Seed(db, "same body");
|
|
var service = Assets(db);
|
|
var saved = await service.SaveCoverLetterAsync(Owner, job.Id, "same body", CoverLetterSources.Manual, null, job.Version, default,
|
|
new() { SenderName = "Reviewed sender", ThemeId = "minimal", AccentColor = "#123456", SourceCvId = 42 });
|
|
Assert.Equal(2, saved!.Revision);
|
|
Assert.Equal(2, saved.CurrentVersion);
|
|
Assert.Equal("Reviewed sender", saved.DocumentSettings!.SenderName);
|
|
Assert.Equal("same body", saved.Text);
|
|
var baseline = await db.CoverLetterVersions.SingleAsync(x => x.Version == 1);
|
|
Assert.Null(baseline.DocumentSettingsJson);
|
|
Assert.Equal(CoverLetterSources.Legacy, baseline.Source);
|
|
var settingsJson = job.CoverLetterDocumentSettingsJson;
|
|
await Update(db, job, "generic body");
|
|
await Controller(db).SaveApplicationDrafts(job.Id, new SaveApplicationDraftsRequest(null, "notes", null, ExpectedVersion: job.Version), default);
|
|
await service.SaveCoverLetterAsync(Owner, job.Id, "dedicated body", CoverLetterSources.Manual, null, job.Version, default);
|
|
Assert.Equal(settingsJson, job.CoverLetterDocumentSettingsJson);
|
|
var noOpRevision = job.Version;
|
|
var noOp = await service.SaveCoverLetterAsync(Owner, job.Id, "dedicated body", CoverLetterSources.Manual, null, job.Version, default,
|
|
CoverLetterDocumentSettingsJson.Deserialize(settingsJson));
|
|
Assert.Equal(noOpRevision + 1, noOp!.Revision);
|
|
Assert.Equal(4, noOp.CurrentVersion);
|
|
var restored = await service.RestoreCoverLetterAsync(Owner, job.Id, 1, job.Version, default);
|
|
Assert.Equal("same body", restored!.Text);
|
|
Assert.Equal("modern", restored.DocumentSettings!.ThemeId);
|
|
Assert.Equal("", restored.DocumentSettings.SenderName);
|
|
Assert.Null(restored.DocumentSettings.SourceCvId);
|
|
Assert.Equal(5, restored.CurrentVersion);
|
|
Assert.Equal(settingsJson, (await db.CoverLetterVersions.SingleAsync(x => x.Version == 2)).DocumentSettingsJson);
|
|
var restoredDesign = await service.RestoreCoverLetterAsync(Owner, job.Id, 2, job.Version, default);
|
|
Assert.Equal("same body", restoredDesign!.Text);
|
|
Assert.Equal("Reviewed sender", restoredDesign.DocumentSettings!.SenderName);
|
|
Assert.Equal(6, restoredDesign.CurrentVersion);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Preview_is_read_only_and_copied_settings_never_follow_later_cv_changes()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
await using var db = Context(connection);
|
|
var job = await Seed(db);
|
|
var cv = new CvVariant { OwnerUserId = Owner, Name = "Source CV", PublicSlug = Guid.NewGuid().ToString("N"), SettingsJson = CvVariantSettingsJson.Serialize(new() { AccentColor = "#123456" }) };
|
|
db.CvVariants.Add(cv);
|
|
await db.SaveChangesAsync();
|
|
var settings = new CoverLetterDocumentSettings { SenderName = "Copied name", SourceCvId = cv.Id, AccentColor = "#123456" };
|
|
var service = Assets(db);
|
|
await service.SaveCoverLetterAsync(Owner, job.Id, "approved body", CoverLetterSources.Manual, null, job.Version, default, settings);
|
|
var revision = job.Version;
|
|
var before = job.CoverLetterDocumentSettingsJson;
|
|
var preview = await service.PreviewCoverLetterAsync(Owner, job.Id, "unapproved preview **bold**", new() { Subject = "Preview only" }, default);
|
|
Assert.Contains("unapproved preview <strong>bold</strong>", preview!.Html);
|
|
cv.SettingsJson = CvVariantSettingsJson.Serialize(new() { AccentColor = "#abcdef" });
|
|
await db.SaveChangesAsync();
|
|
var render = await service.RenderSavedCoverLetterAsync(Owner, job.Id, revision, default);
|
|
Assert.Contains("#123456", render!.Html);
|
|
Assert.Contains("Copied name", render.Html);
|
|
Assert.Contains("approved body", render.Html);
|
|
Assert.DoesNotContain("unapproved preview", render.Html);
|
|
Assert.Equal(before, job.CoverLetterDocumentSettingsJson);
|
|
Assert.Equal(revision, job.Version);
|
|
Assert.Single(await db.CoverLetterVersions.ToListAsync());
|
|
Assert.Null(await service.PreviewCoverLetterAsync("foreign", job.Id, "stolen", settings, default));
|
|
Assert.Null(await service.RenderSavedCoverLetterAsync("foreign", job.Id, revision, default));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(99)]
|
|
public async Task Export_requires_current_positive_revision_before_allocating_pdf(int? expectedVersion)
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
await using var db = Context(connection);
|
|
var job = await Seed(db, "saved body");
|
|
var pdf = new Mock<ICvPdfExporter>(MockBehavior.Strict);
|
|
var user = new ApplicationUser { Id = Owner };
|
|
var users = TestHostFactory.CreateUserManager(user);
|
|
users.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);
|
|
var controller = new ApplicationAssetsController(users.Object, Assets(db), Mock.Of<ISubmittedApplicationPackageService>(), pdf.Object)
|
|
{
|
|
ControllerContext = new() { HttpContext = new DefaultHttpContext() },
|
|
};
|
|
Assert.IsType<ConflictObjectResult>(await controller.ExportCoverLetterPdf(job.Id, default, expectedVersion));
|
|
pdf.VerifyNoOtherCalls();
|
|
pdf.Setup(x => x.ExportAsync(Owner, It.Is<TailoredCvRenderResult>(r => r.Html.Contains("saved body")), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new CvPdfArtifact("cover-letter.pdf", "unused.pdf", [37, 80, 68, 70]));
|
|
Assert.IsType<FileContentResult>(await controller.ExportCoverLetterPdf(job.Id, default, job.Version));
|
|
pdf.Verify(x => x.ExportAsync(Owner, It.IsAny<TailoredCvRenderResult>(), It.IsAny<CancellationToken>()), Times.Once);
|
|
Assert.IsType<NotFoundResult>(await controller.ExportCoverLetterPdf(int.MaxValue, default, job.Version));
|
|
pdf.VerifyNoOtherCalls();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Stale_settings_save_and_unsupported_schema_cannot_change_approved_design()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
await using var db = Context(connection);
|
|
var job = await Seed(db);
|
|
var service = Assets(db);
|
|
await service.SaveCoverLetterAsync(Owner, job.Id, "approved", CoverLetterSources.Manual, null, 1, default, new() { Subject = "Approved subject" });
|
|
await Assert.ThrowsAsync<DbUpdateConcurrencyException>(() => service.SaveCoverLetterAsync(Owner, job.Id, "stale", CoverLetterSources.Manual, null, 1, default, new() { Subject = "Stale" }));
|
|
Assert.Equal("Approved subject", (await service.GetCoverLetterAsync(Owner, job.Id, default))!.DocumentSettings!.Subject);
|
|
var user = new ApplicationUser { Id = Owner };
|
|
var users = TestHostFactory.CreateUserManager(user);
|
|
users.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);
|
|
var controller = new ApplicationAssetsController(users.Object, service, Mock.Of<ISubmittedApplicationPackageService>());
|
|
Assert.IsType<BadRequestObjectResult>((await controller.SaveCoverLetter(job.Id,
|
|
new("bad", null, null, job.Version, new() { SchemaVersion = 2 }), default)).Result);
|
|
Assert.IsType<BadRequestObjectResult>((await controller.PreviewCoverLetter(job.Id,
|
|
new("bad", new() { SchemaVersion = 2 }), default)).Result);
|
|
Assert.Single(await db.CoverLetterVersions.ToListAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Submitted_letter_html_and_design_remain_exact_after_live_edits_and_old_packages_stay_null()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
|
await connection.OpenAsync();
|
|
await using var db = Context(connection);
|
|
var job = await Seed(db);
|
|
var service = Assets(db);
|
|
await service.SaveCoverLetterAsync(Owner, job.Id, "frozen body", CoverLetterSources.Manual, null, job.Version, default,
|
|
new() { SenderName = "Frozen sender", Subject = "Frozen subject" });
|
|
var old = new SubmittedApplicationPackage { OwnerUserId = Owner, JobApplicationId = job.Id, Version = 1, CoverLetterText = "old text-only" };
|
|
db.Add(old);
|
|
await db.SaveChangesAsync();
|
|
var packages = new SubmittedApplicationPackageService(db, Mock.Of<ICvVariantService>(), Mock.Of<IAttachmentStorage>());
|
|
var captured = await packages.CaptureAsync(Owner, job.Id, new("Profile name must not replace sender", null), default);
|
|
var stored = await db.SubmittedApplicationPackages.AsNoTracking().SingleAsync(x => x.Id == captured!.Id);
|
|
Assert.Contains("frozen body", stored.RenderedCoverLetterHtml);
|
|
Assert.Contains("Frozen sender", stored.RenderedCoverLetterHtml);
|
|
Assert.DoesNotContain("Profile name must not replace sender", stored.RenderedCoverLetterHtml);
|
|
await service.SaveCoverLetterAsync(Owner, job.Id, "new body", CoverLetterSources.Manual, null, job.Version, default,
|
|
new() { SenderName = "New sender", Subject = "New subject" });
|
|
var detail = await packages.GetAsync(Owner, job.Id, captured!.Id, default);
|
|
Assert.Equal(stored.RenderedCoverLetterHtml, detail!.RenderedCoverLetterHtml);
|
|
Assert.Equal("Frozen sender", detail.CoverLetterDocumentSettings!.SenderName);
|
|
Assert.Equal("frozen body", detail.CoverLetterText);
|
|
var oldDetail = await packages.GetAsync(Owner, job.Id, old.Id, default);
|
|
Assert.Equal("old text-only", oldDetail!.CoverLetterText);
|
|
Assert.Null(oldDetail.RenderedCoverLetterHtml);
|
|
Assert.Null(oldDetail.CoverLetterDocumentSettings);
|
|
}
|
|
}
|