using System.Security.Claims; using JobTrackerApi.Controllers; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Moq; using Xunit; namespace JobTrackerApi.MariaDb.Tests; public sealed partial class CoverLetterWriterTests { [Fact] public async Task Document_schema_upgrade_preserves_legacy_letter_history_and_frozen_rows_on_mariadb() { await using var connection = await MariaDbDatabase.CreateAsync(); await using var db = Context(connection); var job = await Seed(db, " old exact letter "); db.CoverLetterVersions.Add(new() { OwnerUserId = Owner, JobApplicationId = job.Id, Version = 7, Text = " old history ", Source = CoverLetterSources.Ai }); db.SubmittedApplicationPackages.Add(new() { OwnerUserId = Owner, JobApplicationId = job.Id, Version = 1, CoverLetterText = " old frozen letter ", RenderedCvHtml = "
unchanged CV
" }); await db.SaveChangesAsync(); await db.GetServiceunchanged CV
", frozen.RenderedCvHtml); Assert.Null(frozen.CoverLetterDocumentSettingsJson); Assert.Null(frozen.RenderedCoverLetterHtml); Assert.Empty(await db.Database.GetPendingMigrationsAsync()); } [Fact] public async Task Document_settings_only_saves_advance_shared_revision_preserve_omission_and_restore_old_defaults() { await using var connection = await MariaDbDatabase.CreateAsync(); 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 = await MariaDbDatabase.CreateAsync(); 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 bold", 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 = await MariaDbDatabase.CreateAsync(); await using var db = Context(connection); var job = await Seed(db, "saved body"); var pdf = new Mock