fix(rd21): guard career and job saves with durable revisions
This commit is contained in:
@@ -29,7 +29,7 @@ public sealed class CareerProfileServiceTests
|
||||
Jobs = { new StructuredCvJob { Title = "Engineer", Company = "Acme" } },
|
||||
};
|
||||
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "upload", default);
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "upload", 0, default);
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(saved.Jobs[0].Id));
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public sealed class CareerProfileServiceTests
|
||||
Education = { new StructuredCvEducation { Id = "shared", Qualification = "BSc", Institution = "University" } },
|
||||
};
|
||||
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "manual", default);
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "manual", 0, default);
|
||||
|
||||
Assert.Equal("shared", saved.Jobs[0].Id);
|
||||
Assert.NotEqual(saved.Jobs[0].Id, saved.Education[0].Id);
|
||||
@@ -80,10 +80,10 @@ public sealed class CareerProfileServiceTests
|
||||
{
|
||||
Jobs = { new StructuredCvJob { Title = "Engineer", Company = "Acme" } },
|
||||
};
|
||||
await service.SaveVersionAsync("user-1", profile, "upload", default);
|
||||
await service.SaveVersionAsync("user-1", profile, "upload", 0, default);
|
||||
var firstId = profile.Jobs[0].Id;
|
||||
|
||||
await service.SaveVersionAsync("user-1", profile, "rebuild", default);
|
||||
await service.SaveVersionAsync("user-1", profile, "rebuild", 1, default);
|
||||
|
||||
Assert.Equal(firstId, profile.Jobs[0].Id);
|
||||
}
|
||||
@@ -108,8 +108,8 @@ public sealed class CareerProfileServiceTests
|
||||
var service = new CareerProfileService(db);
|
||||
var profile = new StructuredCvProfile { Summary = { "First version" } };
|
||||
|
||||
await service.SaveVersionAsync("user-1", profile, "upload", default);
|
||||
await service.SaveVersionAsync("user-1", profile, "improve", default);
|
||||
await service.SaveVersionAsync("user-1", profile, "upload", 0, default);
|
||||
await service.SaveVersionAsync("user-1", profile, "improve", 1, default);
|
||||
|
||||
var snapshots = await db.CareerProfiles.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1").ToListAsync();
|
||||
var history = await db.CareerProfileVersions.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1").ToListAsync();
|
||||
@@ -130,7 +130,7 @@ public sealed class CareerProfileServiceTests
|
||||
Jobs = { new StructuredCvJob { Title = "Engineer", Start = "Jan 2020", End = "Present", IsCurrent = true } },
|
||||
};
|
||||
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "upload", default);
|
||||
var saved = await service.SaveVersionAsync("user-1", profile, "upload", 0, default);
|
||||
|
||||
Assert.Equal("2020-01", saved.Jobs[0].StartDate);
|
||||
Assert.Null(saved.Jobs[0].EndDate);
|
||||
@@ -165,7 +165,7 @@ public sealed class CareerProfileServiceTests
|
||||
await using var db = NewContext("user-1");
|
||||
var service = new CareerProfileService(db);
|
||||
|
||||
await service.SaveVersionAsync("user-1", RichProfile(), "manual", default);
|
||||
await service.SaveVersionAsync("user-1", RichProfile(), "manual", 0, default);
|
||||
var loaded = await service.LoadStructuredAsync("user-1", default);
|
||||
|
||||
Assert.Equal("Ada Lovelace", loaded.Contact.FullName);
|
||||
@@ -196,7 +196,7 @@ public sealed class CareerProfileServiceTests
|
||||
var service = new CareerProfileService(db);
|
||||
var profile = RichProfile();
|
||||
|
||||
await service.SaveVersionAsync("user-1", profile, "manual", default);
|
||||
await service.SaveVersionAsync("user-1", profile, "manual", 0, default);
|
||||
var jobKey = profile.Jobs[0].Id;
|
||||
var loaded = await service.LoadStructuredAsync("user-1", default);
|
||||
|
||||
@@ -213,9 +213,9 @@ public sealed class CareerProfileServiceTests
|
||||
await using var db = NewContext("user-1");
|
||||
var service = new CareerProfileService(db);
|
||||
|
||||
await service.SaveVersionAsync("user-1", RichProfile(), "manual", default); // 2 jobs
|
||||
await service.SaveVersionAsync("user-1", RichProfile(), "manual", 0, default); // 2 jobs
|
||||
var trimmed = new StructuredCvProfile { Jobs = { new StructuredCvJob { Title = "Only role", Company = "Solo" } } };
|
||||
await service.SaveVersionAsync("user-1", trimmed, "manual", default); // 1 job
|
||||
await service.SaveVersionAsync("user-1", trimmed, "manual", 1, default); // 1 job
|
||||
|
||||
var experiences = await db.CareerExperiences.IgnoreQueryFilters().Where(x => x.OwnerUserId == "user-1").ToListAsync();
|
||||
Assert.Single(experiences);
|
||||
@@ -225,7 +225,7 @@ public sealed class CareerProfileServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Load_backfills_relational_children_from_a_pre_phase3_blob()
|
||||
public async Task Load_reads_a_pre_phase3_blob_without_mutating_canonical_children()
|
||||
{
|
||||
await using var db = NewContext("user-1");
|
||||
// Simulate a profile that predates Phase 3: a CareerProfile with a ProfileJson blob but no
|
||||
@@ -239,8 +239,8 @@ public sealed class CareerProfileServiceTests
|
||||
var loaded = await service.LoadStructuredAsync("user-1", default);
|
||||
|
||||
Assert.Equal(2, loaded.Jobs.Count);
|
||||
// The backfill materialized the relational rows.
|
||||
Assert.Equal(2, await db.CareerExperiences.IgnoreQueryFilters().CountAsync(x => x.OwnerUserId == "user-1"));
|
||||
// Reads cannot materialize unversioned children alongside a concurrent replacement.
|
||||
Assert.Equal(0, await db.CareerExperiences.IgnoreQueryFilters().CountAsync(x => x.OwnerUserId == "user-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -260,8 +260,8 @@ public sealed class CareerProfileServiceTests
|
||||
{
|
||||
await using var db = NewContext("user-1");
|
||||
var service = new CareerProfileService(db);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "v1" } }, "upload", default);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "v2" } }, "improve", default);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "v1" } }, "upload", 0, default);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "v2" } }, "improve", 1, default);
|
||||
|
||||
var versions = await service.ListVersionsAsync("user-1", default);
|
||||
|
||||
@@ -276,10 +276,10 @@ public sealed class CareerProfileServiceTests
|
||||
{
|
||||
await using var db = NewContext("user-1");
|
||||
var service = new CareerProfileService(db);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "original" } }, "upload", default); // v1
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "edited" } }, "manual", default); // v2
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "original" } }, "upload", 0, default); // v1
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile { Summary = { "edited" } }, "manual", 1, default); // v2
|
||||
|
||||
var restored = await service.RestoreVersionAsync("user-1", 1, default);
|
||||
var restored = await service.RestoreVersionAsync("user-1", 1, 2, default);
|
||||
|
||||
Assert.NotNull(restored);
|
||||
Assert.Equal(new[] { "original" }, restored!.Summary);
|
||||
@@ -296,8 +296,8 @@ public sealed class CareerProfileServiceTests
|
||||
{
|
||||
await using var db = NewContext("user-1");
|
||||
var service = new CareerProfileService(db);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile(), "upload", default);
|
||||
await service.SaveVersionAsync("user-1", new StructuredCvProfile(), "upload", 0, default);
|
||||
|
||||
Assert.Null(await service.RestoreVersionAsync("user-1", 99, default));
|
||||
Assert.Null(await service.RestoreVersionAsync("user-1", 99, 1, default));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user