Files
jobtrackingapp/JobTrackerApi.MariaDb.Tests/CvBlankPersistenceTests.cs
T
2026-10-27 07:37:03 +01:00

147 lines
8.0 KiB
C#

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<DbUpdateConcurrencyException>(() => Service(staleSave).SaveAsync(Owner, id, "Stale name", Blank(), "autosave", 1, default));
await Assert.ThrowsAsync<DbUpdateConcurrencyException>(() => 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);
}
}