fix(workspace): clear native package and navigation readiness gates
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
using System.Security.Claims;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using JobTrackerApi.Controllers;
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
@@ -17,6 +21,79 @@ namespace JobTrackerApi.Tests;
|
||||
|
||||
public sealed class SqliteDateTimeOffsetCompatibilityTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Tailoring_and_submission_capture_work_on_native_sqlite()
|
||||
{
|
||||
await using var fixture = await SqliteFixture.CreateAsync("user-1");
|
||||
var db = fixture.Db;
|
||||
var company = new Company { OwnerUserId = "user-1", Name = "Nordlys AS" };
|
||||
db.Companies.Add(company);
|
||||
await db.SaveChangesAsync();
|
||||
var job = new JobApplication { OwnerUserId = "user-1", CompanyId = company.Id, JobTitle = "Engineer", Status = "Saved", Description = "C# engineer", CoverLetterText = "Søknad — æøå" };
|
||||
db.JobApplications.Add(job);
|
||||
await db.SaveChangesAsync();
|
||||
db.CvVariants.Add(Variant("user-1", job.Id, "Submitted CV", DateTimeOffset.UtcNow));
|
||||
db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "native@example.test" });
|
||||
var storage = new AttachmentStorage(fixture.Paths);
|
||||
var source = storage.CreateFinalPath(job.Id, "evidence.txt");
|
||||
var bytes = Encoding.UTF8.GetBytes("Preserved evidence — æøå\n");
|
||||
await File.WriteAllBytesAsync(source, bytes);
|
||||
var attachment = new Attachment { JobApplicationId = job.Id, FileName = "evidence.txt", FilePath = source, FileType = "text/plain", FileSize = bytes.Length, Purpose = "portfolio" };
|
||||
db.Attachments.Add(attachment);
|
||||
await db.SaveChangesAsync();
|
||||
var career = new CareerProfileService(db);
|
||||
await career.SaveVersionAsync("user-1", JsonSerializer.Deserialize<StructuredCvProfile>("{\"version\":\"1\",\"contact\":{\"fullName\":\"Åse\",\"headline\":\"Original profile\"}}", new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!, "manual", default);
|
||||
var variants = new CvVariantService(db, career, new ThemedCvRenderer());
|
||||
var assets = new ApplicationAssetsService(db, variants, new ApplicationIntelligenceService(db, new JobCvMatchService()));
|
||||
Assert.NotNull(await assets.GetTailoringPlanAsync("user-1", job.Id, default));
|
||||
var packages = new SubmittedApplicationPackageService(db, variants, storage);
|
||||
var captured = await packages.CaptureAsync("user-1", job.Id, new CvRenderPerson("Åse", null), default);
|
||||
Assert.NotNull(captured);
|
||||
Assert.Equal("Søknad — æøå", (await packages.GetAsync("user-1", job.Id, captured.Id, default))!.CoverLetterText);
|
||||
Assert.Null(await packages.GetAsync("user-2", job.Id, captured.Id, default));
|
||||
var before = await packages.GetAsync("user-1", job.Id, captured.Id, default);
|
||||
Assert.Contains("Original profile", before!.RenderedCvHtml);
|
||||
var frozenFile = Assert.Single(captured.Attachments);
|
||||
Assert.Equal(Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(), frozenFile.Sha256);
|
||||
Assert.Null(await packages.GetAttachmentAsync("user-2", job.Id, captured.Id, frozenFile.Id, default));
|
||||
job.CoverLetterText = "Later letter";
|
||||
(await db.CareerProfiles.SingleAsync()).ProfileJson = "{}";
|
||||
(await db.CvVariants.SingleAsync()).Name = "Later CV";
|
||||
db.Attachments.Remove(attachment);
|
||||
await db.SaveChangesAsync();
|
||||
File.Delete(source);
|
||||
var after = await packages.GetAsync("user-1", job.Id, captured.Id, default);
|
||||
Assert.Equal(before.RenderedCvHtml, after!.RenderedCvHtml);
|
||||
Assert.Equal(before.CoverLetterText, after.CoverLetterText);
|
||||
Assert.Equal(before.ApplicationMaterialJson, after.ApplicationMaterialJson);
|
||||
Assert.Equal(before.Summary.CvVariantVersion, after.Summary.CvVariantVersion);
|
||||
var downloaded = await packages.GetAttachmentAsync("user-1", job.Id, captured.Id, frozenFile.Id, default);
|
||||
Assert.Equal(bytes, await File.ReadAllBytesAsync(downloaded!.Path));
|
||||
var exporter = new AccountDataExportService(db, fixture.Paths, new AccountOwnedFileInventory(db, fixture.Paths, storage), TimeProvider.System);
|
||||
var exported = await exporter.CreateAsync("user-1", default);
|
||||
using var zip = ZipFile.OpenRead(exported.StoragePath);
|
||||
using var workspaceStream = zip.GetEntry("data/application-workspace.json")!.Open();
|
||||
using var workspace = await JsonDocument.ParseAsync(workspaceStream);
|
||||
var frozen = Assert.Single(workspace.RootElement.GetProperty("submittedApplicationPackages").EnumerateArray());
|
||||
Assert.Equal(captured.Id, frozen.GetProperty("id").GetInt32());
|
||||
Assert.Equal(job.Id, frozen.GetProperty("jobApplicationId").GetInt32());
|
||||
Assert.Equal(before.RenderedCvHtml, frozen.GetProperty("renderedCvHtml").GetString());
|
||||
Assert.Equal(before.CoverLetterText, frozen.GetProperty("coverLetterText").GetString());
|
||||
Assert.Equal(before.ApplicationMaterialJson, frozen.GetProperty("applicationMaterialJson").GetString());
|
||||
Assert.Equal("Submitted CV", frozen.GetProperty("cvVariantName").GetString());
|
||||
Assert.Contains("Original profile", frozen.GetProperty("careerProfileJson").GetString());
|
||||
Assert.Equal("portfolio", Assert.Single(frozen.GetProperty("attachments").EnumerateArray()).GetProperty("purpose").GetString());
|
||||
var path = $"files/submitted-packages/{captured.Id}/{frozenFile.Id}/evidence.txt";
|
||||
using var entry = zip.GetEntry(path)!.Open();
|
||||
using var readback = new MemoryStream();
|
||||
await entry.CopyToAsync(readback);
|
||||
Assert.Equal(bytes, readback.ToArray());
|
||||
using var manifestStream = zip.GetEntry("manifest.json")!.Open();
|
||||
using var manifest = await JsonDocument.ParseAsync(manifestStream);
|
||||
var metadata = manifest.RootElement.GetProperty("entries").EnumerateArray().Single(x => x.GetProperty("path").GetString() == path);
|
||||
Assert.Equal(frozenFile.Sha256, metadata.GetProperty("sha256").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MariaDb_branch_translates_the_same_date_ordering_and_range_queries()
|
||||
{
|
||||
@@ -37,6 +114,40 @@ public sealed class SqliteDateTimeOffsetCompatibilityTests
|
||||
Assert.Contains("CreatedAtUtc", rangeSql, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Native_sparse_capture_and_missing_attachment_retry_never_publish_partial_packages()
|
||||
{
|
||||
await using var fixture = await SqliteFixture.CreateAsync("user-1");
|
||||
var db = fixture.Db;
|
||||
var company = new Company { OwnerUserId = "user-1", Name = "Sparse AS" };
|
||||
db.Companies.Add(company);
|
||||
await db.SaveChangesAsync();
|
||||
var job = new JobApplication { OwnerUserId = "user-1", CompanyId = company.Id, JobTitle = "Sparse role", Status = "Saved" };
|
||||
db.JobApplications.Add(job);
|
||||
await db.SaveChangesAsync();
|
||||
var storage = new AttachmentStorage(fixture.Paths);
|
||||
var packages = new SubmittedApplicationPackageService(db, new CvVariantService(db, new CareerProfileService(db), new ThemedCvRenderer()), storage);
|
||||
var sparse = await packages.CaptureAsync("user-1", job.Id, new CvRenderPerson("Åse", null), default);
|
||||
Assert.False(sparse!.HasCv);
|
||||
Assert.False(sparse.HasCoverLetter);
|
||||
Assert.Empty(sparse.Attachments);
|
||||
var availableSource = storage.CreateFinalPath(job.Id, "available.txt");
|
||||
await File.WriteAllTextAsync(availableSource, "Already available native bytes");
|
||||
db.Attachments.Add(new Attachment { JobApplicationId = job.Id, FileName = "available.txt", FilePath = availableSource, FileType = "text/plain" });
|
||||
await db.SaveChangesAsync();
|
||||
var source = storage.CreateFinalPath(job.Id, "retry.txt");
|
||||
db.Attachments.Add(new Attachment { JobApplicationId = job.Id, FileName = "retry.txt", FilePath = source, FileType = "text/plain" });
|
||||
await db.SaveChangesAsync();
|
||||
await Assert.ThrowsAsync<IOException>(() => packages.CaptureAsync("user-1", job.Id, new CvRenderPerson("Åse", null), default));
|
||||
Assert.Single((await packages.ListAsync("user-1", job.Id, default))!);
|
||||
Assert.Empty(Directory.GetFiles(Path.Combine(fixture.Paths.AttachmentsRoot, job.Id.ToString(), "submitted", "2")));
|
||||
await File.WriteAllTextAsync(source, "Retried native bytes");
|
||||
var retried = await packages.CaptureAsync("user-1", job.Id, new CvRenderPerson("Åse", null), default);
|
||||
Assert.Equal(2, retried!.Version);
|
||||
Assert.Equal(2, retried.Attachments.Count);
|
||||
Assert.Equal(2, (await packages.ListAsync("user-1", job.Id, default))!.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Affected_workspace_queries_order_dates_and_preserve_owner_scope()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user