using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.EntityFrameworkCore; using Xunit; namespace JobTrackerApi.MariaDb.Tests; public sealed class CvBlankPersistenceTests { private const string Owner = "rd21-owner"; private static CvVariantService Service(JobTrackerContext db) => new(db, new CareerProfileService(db), new ThemedCvRenderer()); private static CvVariantSettings Blank() => new() { SectionsExplicit = true, IncludeProfileContact = false }; private static async Task SeedProfile(JobTrackerContext db) { db.Users.Add(new() { Id = Owner, UserName = "synthetic-cv-blank" }); await db.SaveChangesAsync(); await new CareerProfileService(db).SaveVersionAsync(Owner, new() { Contact = new() { FullName = "Canonical Person", Headline = "Canonical headline", Email = "synthetic@example.test" }, Summary = { "Canonical summary" }, Jobs = { new() { Id = "canonical-role", Title = "Canonical role", Company = "Synthetic company", Bullets = { "Canonical evidence" } } }, Skills = { "Canonical skill" }, }, "manual", 0, default); } [Fact] public async Task Blank_json_readback_duplicate_design_and_restore_preserve_canonical_profile_on_mariadb() { await using var database = await MariaDbDatabase.CreateAsync(); int id; string initialJson; string canonicalJson; string? identityJson; await using (var seed = database.Context()) { await SeedProfile(seed); canonicalJson = (await seed.CareerProfiles.SingleAsync()).ProfileJson; identityJson = (await seed.Users.SingleAsync()).ProfileCvStructureJson; var created = await Service(seed).CreateAsync(Owner, "Blank", null, Blank(), default); id = created.Id; initialJson = created.SettingsJson; } int copyId; await using (var edit = database.Context()) { var service = Service(edit); var read = (await service.GetAsync(Owner, id, default))!; Assert.Equal(initialJson, read.SettingsJson); var settings = CvVariantSettingsJson.Deserialize(read.SettingsJson); Assert.True(settings.SectionsExplicit); Assert.False(settings.IncludeProfileContact); Assert.Empty(settings.Sections); var rendered = await service.RenderAsync(Owner, id, new("Fallback identity", "data:image/png;base64,AA=="), default, 1); Assert.False(rendered!.HasContent); Assert.DoesNotContain("Canonical", rendered.Html); Assert.DoesNotContain("Fallback identity", rendered.Html); var copy = (await service.DuplicateAsync(Owner, id, "Blank copy", default))!; copyId = copy.Id; Assert.Equal(initialJson, copy.SettingsJson); settings.ThemeId = "minimal"; settings.AccentColor = "#123456"; await service.SaveAsync(Owner, id, null, settings, "manual", 1, default); } await using (var reopen = database.Context()) { var service = Service(reopen); var settings = CvVariantSettingsJson.Deserialize((await service.GetAsync(Owner, id, default))!.SettingsJson); Assert.Equal("minimal", settings.ThemeId); Assert.True(settings.SectionsExplicit); Assert.False(settings.IncludeProfileContact); Assert.False((await service.RenderAsync(Owner, id, new("Fallback", null), default, 2))!.HasContent); settings.Headline = "Authored CV-only headline"; await service.SaveAsync(Owner, id, null, settings, "manual", 2, default); var restored = (await service.RestoreVersionAsync(Owner, id, 1, 3, default))!; Assert.Equal(4, restored.Version); Assert.Equal(initialJson, restored.SettingsJson); } await using var verify = database.Context(); Assert.Equal(initialJson, (await verify.CvVariants.SingleAsync(cv => cv.Id == id)).SettingsJson); Assert.Equal(initialJson, (await verify.CvVariants.SingleAsync(cv => cv.Id == copyId)).SettingsJson); Assert.Equal(new[] { 1, 2, 3, 4 }, await verify.CvVariantVersions.Where(row => row.CvVariantId == id).OrderBy(row => row.Version).Select(row => row.Version).ToArrayAsync()); Assert.Single(await verify.CvVariantVersions.Where(row => row.CvVariantId == copyId).ToListAsync()); Assert.Equal(canonicalJson, (await verify.CareerProfiles.SingleAsync()).ProfileJson); Assert.Equal(identityJson, (await verify.Users.SingleAsync()).ProfileCvStructureJson); Assert.Equal(1, (await verify.CareerProfiles.SingleAsync()).Version); Assert.Single(await verify.CareerProfileVersions.ToListAsync()); Assert.Equal("Canonical role", (await verify.CareerExperiences.SingleAsync()).Title); var profile = await new CareerProfileService(verify).LoadStructuredAsync(Owner, default); Assert.Equal("Canonical summary", Assert.Single(profile.Summary)); Assert.Equal("Canonical skill", Assert.Single(profile.Skills)); Assert.Equal("Canonical evidence", Assert.Single(Assert.Single(profile.Jobs).Bullets)); } [Fact] public async Task Competing_blank_edits_and_foreign_access_cannot_overwrite_winner_or_append_history_on_mariadb() { await using var database = await MariaDbDatabase.CreateAsync(); int id; await using (var seed = database.Context()) { await SeedProfile(seed); id = (await Service(seed).CreateAsync(Owner, "Blank", null, Blank(), default)).Id; } await using var winner = database.Context(); await using var staleSave = database.Context(); await using var staleRestore = database.Context(); Assert.Equal(1, (await Service(staleSave).GetAsync(Owner, id, default))!.Version); Assert.Equal(1, (await Service(staleRestore).GetAsync(Owner, id, default))!.Version); var settings = Blank(); settings.ThemeId = "minimal"; settings.Headline = "Acknowledged CV-only winner"; await Service(winner).SaveAsync(Owner, id, "Winner", settings, "manual", 1, default); await Assert.ThrowsAsync(() => Service(staleSave).SaveAsync(Owner, id, "Stale name", Blank(), "autosave", 1, default)); await Assert.ThrowsAsync(() => Service(staleRestore).RestoreVersionAsync(Owner, id, 1, 1, default)); await using (var foreign = database.Context("foreign")) { var service = Service(foreign); Assert.Null(await service.GetAsync("foreign", id, default)); Assert.Null(await service.SaveAsync("foreign", id, "Foreign", Blank(), "manual", 2, default)); Assert.Null(await service.DuplicateAsync("foreign", id, null, default)); Assert.Null(await service.RestoreVersionAsync("foreign", id, 1, 2, default)); Assert.Null(await service.RenderAsync("foreign", id, new("Foreign", null), default, 2)); Assert.Empty(await service.ListVersionsAsync("foreign", id, default)); } await using var verify = database.Context(); var saved = await verify.CvVariants.SingleAsync(); Assert.Equal(2, saved.Version); Assert.Equal("Winner", saved.Name); Assert.Equal(CvVariantSettingsJson.Serialize(settings), saved.SettingsJson); var history = await verify.CvVariantVersions.OrderBy(row => row.Version).ToListAsync(); Assert.Equal(new[] { 1, 2 }, history.Select(row => row.Version)); Assert.Equal(CvVariantSettingsJson.Serialize(Blank()), history[0].SettingsJson); Assert.Equal(saved.SettingsJson, history[1].SettingsJson); Assert.Equal(1, (await verify.CareerProfiles.SingleAsync()).Version); Assert.Single(await verify.CareerProfileVersions.ToListAsync()); Assert.Equal("Canonical role", (await verify.CareerExperiences.SingleAsync()).Title); } }