using System.Net.Http.Json; using System.Text.Json; using DodoSSH.Contracts; namespace DodoSSH.Api.Tests; /// /// Sends and reads request bodies the way a real client does. /// /// /// /// Not a convenience. PostAsJsonAsync's default options write an enum as a number, and the shared /// contract writes it as a string. Tests that used the defaults therefore agreed with a server that had /// also been left on the defaults, and the pair of them agreed on a wire form the specification never /// described — so the entire sync surface was unreachable from the real client and every test passed. /// /// /// Everything here goes through for that reason. If the server's JSON /// configuration regresses, these tests are the ones that must fail. /// /// internal static class ContractJson { private static JsonSerializerOptions Options => DodoSshJsonContext.ResponseOptions; internal static Task PostContractAsync( this HttpClient client, string url, T value) { ArgumentNullException.ThrowIfNull(client); return client.PostAsJsonAsync(url, value, Options, TestContext.Current.CancellationToken); } /// /// The same options as , and here for the same reason rather than /// for symmetry: a PUT that serialised its enums differently from a POST would let a wire form the /// specification never described reach exactly the endpoints nothing else covers. /// internal static Task PutContractAsync( this HttpClient client, string url, T value) { ArgumentNullException.ThrowIfNull(client); return client.PutAsJsonAsync(url, value, Options, TestContext.Current.CancellationToken); } internal static Task ReadContractAsync(this HttpContent content) { ArgumentNullException.ThrowIfNull(content); return content.ReadFromJsonAsync(Options, TestContext.Current.CancellationToken); } /// Reads an RFC 9457 problem body. /// /// Deliberately not through the contract options. Problem details are written by the framework /// and are not part of 's source-generated set, so resolving them /// against it fails outright — a source-generated context does not fall back to reflection. Reading /// them with the ambient web options is correct rather than a shortcut: the shape is the RFC's, not /// ours, and only the code extension belongs to us. /// internal static Task ReadProblemAsync(this HttpContent content) { ArgumentNullException.ThrowIfNull(content); return content.ReadFromJsonAsync(TestContext.Current.CancellationToken); } }