`testing.mutation-claims-are-executed` was amended on main while this branch was in review (#881, merged as #914): a sentence asserting that a specific mutation reddens — or does not redden — a named test is now either a `CLAIMS` entry in `scripts/tests/mutation_manifest.py` that executes every run, or it is not written. This branch carried six such sentences and none of them can be declared: `Claim.node_id` resolves a proof to `scripts/tests/<node id>` and `run_pytest` invokes pytest, so an NUnit proof has no representation in that harness at all. Durable prose now states the mechanism each test is built on — which serializer difference, which engine branch — which a reader re-checks by reading the code rather than by trusting a remembered outcome. The record says that in one paragraph, so the limit is stated rather than papered over. The outcomes themselves are here. Re-measured 2026-09-05 on this branch's tree (the commit before this one), each mutant applied to the working tree and restored from the index between runs, tree verified clean afterwards: positive control ScriptedScheduleControllerTests Passed: 9, Failed: 0 OpenApiSerializerContractTests Passed: 4, Failed: 0 Bind<T> -> System.Text.Json with JsonSerializerDefaults.Web Failed: 2, Passed: 7 — Production_Body_Binder_Ignores_Required_Members, Production_Body_Binder_Keeps_Declared_Defaults_Over_An_Explicit_Null BodyBinderSettings = ApiJsonSettings.Create() -> new JsonSerializerSettings() Failed: 1, Passed: 8 — Production_Body_Binder_Keeps_Declared_Defaults_Over_An_Explicit_Null OpenApiSerializerContractTests RuntimeSettings -> new JsonSerializerSettings() Failed: 4, Passed: 0 — all four cases, on PascalCase keys ScriptedScheduleController AddDuration(..., request.Trim, ...) -> false Failed: 1, Passed: 8 — Committed_Script_Fixture_Produces_The_Pinned_Snapshot ScriptedScheduleController PadUntilExact(..., request.Trim, ...) -> false Failed: 1, Passed: 8 — Committed_Script_Fixture_Produces_The_Pinned_Snapshot The last one is the round-two finding closed and re-witnessed: before the fixture's pad target moved off the content boundary, that mutant left all nine green. A squash merge writes its own message, so these figures also belong in the PR description. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
44 lines
2.7 KiB
C#
44 lines
2.7 KiB
C#
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Converters;
|
|
|
|
namespace ErsatzTV.Serialization;
|
|
|
|
/// <summary>
|
|
/// The Newtonsoft settings the MVC JSON formatters read and write `/api/*` bodies with. Defined once,
|
|
/// here, so a test can deserialize a request body exactly the way the production body binder does
|
|
/// instead of mirroring <see cref="Startup" />'s registration. This REMOVES the duplicate rather than
|
|
/// detecting drift in one: no test observes <c>Startup.ConfigureServices</c>, and a byte-equal hand-copy
|
|
/// of <see cref="Apply" /> is behaviourally indistinguishable from calling it. What the tests do
|
|
/// separate is a caller that binds with something else — a body omitting a C#
|
|
/// <c>required</c> member deserializes to a default here where System.Text.Json throws, and
|
|
/// <see cref="NullValueHandling" />.<c>Ignore</c> keeps a DTO's declared default over an explicit
|
|
/// null where plain Newtonsoft writes the null through. Both are pinned by
|
|
/// <c>ScriptedScheduleControllerTests</c>; the naming strategy is pinned on the write side by
|
|
/// <c>OpenApiSerializerContractTests</c>. See docs/testing.md → "Scripted playout coverage".
|
|
/// </summary>
|
|
public static class ApiJsonSettings
|
|
{
|
|
/// <summary>Applies the production configuration to an existing settings object (the MVC path).</summary>
|
|
public static JsonSerializerSettings Apply(JsonSerializerSettings settings)
|
|
{
|
|
settings.NullValueHandling = NullValueHandling.Ignore;
|
|
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
settings.ContractResolver = new CustomContractResolver();
|
|
settings.Converters.Add(new StringEnumConverter());
|
|
return settings;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="Apply" /> onto a bare settings object, for a caller outside the MVC pipeline. It carries
|
|
/// everything <see cref="Apply" /> sets and nothing MVC supplies around it, so it is NOT the object the
|
|
/// body binder runs. Measured 2026-09-05 against <c>new MvcNewtonsoftJsonOptions().SerializerSettings</c>:
|
|
/// <c>MaxDepth</c> is Newtonsoft's 64 rather than MVC's stricter 32, and MVC's two error-shaping
|
|
/// converters — <c>ProblemDetailsConverter</c> and <c>ValidationProblemDetailsConverter</c> — are absent.
|
|
/// <c>MissingMemberHandling</c>, <c>TypeNameHandling</c> and <c>DateParseHandling</c> match. Both gaps are
|
|
/// inert for the request DTOs the tests bind (two levels deep, never a <c>ProblemDetails</c>), which is why
|
|
/// this is usable there at all; the delta is pinned by <c>ApiJsonSettingsTests</c> so it cannot decay into
|
|
/// a parity claim.
|
|
/// </summary>
|
|
public static JsonSerializerSettings Create() => Apply(new JsonSerializerSettings());
|
|
}
|