yavsc/src/PostIt.Tests/BlogApiTestFakes.cs

65 lines
2.4 KiB
C#
Raw Normal View History

refactor(model): move BlogPost DTO from PostIt.Models to Yavsc.Blogspot BlogPost is shared between the server (Yavsc.Server/Models/Blog/ BlogPost.cs is the EF entity) and any client that talks to the blogs API. Keeping the client-side DTO in PostIt.Models made sense when there was only one consumer; now that the Yavsc.Api.Client project is about to host BlogApiClient alongside CircleApiClient and BlogAclApiClient, the DTO has to live in a layer both the client project and PostIt can reference without inverting the dependency. Yavsc.Abstract is the existing home for cross-tier interfaces and DTOs (IBlogPost, IBlogPostPayLoad, IApplicationUser). Yavsc.Blogspot is the sub-namespace already used by the matching interface, so the new concrete class follows. Why not move Circle and CircleAuthorizationToBlogPost at the same time? Both depend on the concrete ApplicationUser class (via the Owner and Target/Allowed navigation properties) which lives in Yavsc.Server. Moving them would mean either dragging ApplicationUser into the abstract layer (huge blast radius — auth, billing, chat, etc.) or weakening the navigation properties (breaks EF Core shaping). They're staying where they are; the new Yavsc.Api.Client will get DTO counterparts instead. Updated call sites: - 4 .cs files: replace 'using PostIt.Models;' with 'using Yavsc.Blogspot;' where the file was actually using BlogPost. Files that only used SignaturePadData keep their 'using PostIt.Models;' — that type stays put. - 1 .axaml file: xmlns:models="using:PostIt.Models" -> xmlns:models="using:Yavsc.Blogspot" (one DataTemplate for the post list in MainPage). Build + tests green (51/51).
2026-08-17 23:45:45 +01:00
using Yavsc.Blogspot;
PostIt/Yavsc.Blogs: surface 4xx body + pin controller + red UI test for Save The 'Save' button in PostIt has been returning 400 from /api/v1/blog ever since the editor's title and article fields were re-bound to SelectedPost.Title / SelectedPost.Article. The user types into the editor, taps Save, the controller rejects with 'The Title field is required', and the PostIt status bar shows only the generic 'Response status code does not indicate success: 400' — no field name, no reason. Three pieces here make the regression diagnosable and pin a test for the fix: 1. YavscApiClient: replace EnsureSuccessStatusCode() at both call sites with a small helper that reads the response body and embeds it in the thrown HttpRequestException. The VM's existing catch (Exception) in ExecuteAsync forwards ex.Message to the status bar, so the next 'click Save' tells the user exactly which field the server rejected. 2. Yavsc.Blogs.Tests: two integration tests on the real controller (no HTTP mock) — one pins that a well-formed PostIt-shaped payload (Title + Article + AuthorId + dates, Id=0) is accepted with 201, the other pins that a payload with Title=string.Empty is rejected with 400. Together they pin the contract the VM has to honour. 3. PostIt.Tests: a red [AvaloniaFact] UI test that mounts MainPage inside a headless Window, types a title into the TextBox without first selecting a post in the list, taps Save, and asserts the body of the first POST contains the typed title. Today this test fails with Title='', reproducing the production 400. The matching fix (a Title/Article buffer on MainPageViewModel that the XAML binds to, and that Save uses to build the outgoing BlogPost) is the next commit; the test is the safety net.
2026-07-11 02:52:50 +01:00
using PostIt.Services;
using PostIt.ViewModels;
namespace PostIt.Tests;
/// <summary>Per-call ledger shared between the test and the
/// recording fake, so the assertion can inspect what the VM
/// actually sent on the wire without coupling to the fake's
/// internals.</summary>
internal sealed class CallRecorder
{
public (HttpMethod method, string path, object? body) FirstCall =>
Calls[0];
public List<(HttpMethod method, string path, object? body)> Calls { get; } = new();
}
/// <summary>Test fake that records every CallAsync invocation
/// and answers them with a canned sequence: the first call gets
/// a server-issued BlogPostDto (Id=42), the second call gets a
PostIt/Yavsc.Blogs: surface 4xx body + pin controller + red UI test for Save The 'Save' button in PostIt has been returning 400 from /api/v1/blog ever since the editor's title and article fields were re-bound to SelectedPost.Title / SelectedPost.Article. The user types into the editor, taps Save, the controller rejects with 'The Title field is required', and the PostIt status bar shows only the generic 'Response status code does not indicate success: 400' — no field name, no reason. Three pieces here make the regression diagnosable and pin a test for the fix: 1. YavscApiClient: replace EnsureSuccessStatusCode() at both call sites with a small helper that reads the response body and embeds it in the thrown HttpRequestException. The VM's existing catch (Exception) in ExecuteAsync forwards ex.Message to the status bar, so the next 'click Save' tells the user exactly which field the server rejected. 2. Yavsc.Blogs.Tests: two integration tests on the real controller (no HTTP mock) — one pins that a well-formed PostIt-shaped payload (Title + Article + AuthorId + dates, Id=0) is accepted with 201, the other pins that a payload with Title=string.Empty is rejected with 400. Together they pin the contract the VM has to honour. 3. PostIt.Tests: a red [AvaloniaFact] UI test that mounts MainPage inside a headless Window, types a title into the TextBox without first selecting a post in the list, taps Save, and asserts the body of the first POST contains the typed title. Today this test fails with Title='', reproducing the production 400. The matching fix (a Title/Article buffer on MainPageViewModel that the XAML binds to, and that Save uses to build the outgoing BlogPost) is the next commit; the test is the safety net.
2026-07-11 02:52:50 +01:00
/// single-element list containing that post. Used by the ViewModel
/// tests and the headless UI test to capture exactly what the
/// Save button posts to the server.</summary>
internal sealed class RecordingYavscApiClient : YavscApiClient
{
private readonly CallRecorder _recorder;
public RecordingYavscApiClient(CallRecorder recorder)
: base(
new Settings
{
Authentication = new AuthenticationSettings
{
Authority = "https://stub.invalid",
ClientId = "stub",
Scopes = new[] { "openid" },
},
},
new TokenStore(System.IO.Path.GetTempFileName()))
{
_recorder = recorder;
}
public override Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
{
_recorder.Calls.Add((method, path, body));
// BlogPostDto? boxes to BlogPostDto at runtime, so we test the
// non-nullable type — typeof(BlogPostDto?) is a C# error
PostIt/Yavsc.Blogs: surface 4xx body + pin controller + red UI test for Save The 'Save' button in PostIt has been returning 400 from /api/v1/blog ever since the editor's title and article fields were re-bound to SelectedPost.Title / SelectedPost.Article. The user types into the editor, taps Save, the controller rejects with 'The Title field is required', and the PostIt status bar shows only the generic 'Response status code does not indicate success: 400' — no field name, no reason. Three pieces here make the regression diagnosable and pin a test for the fix: 1. YavscApiClient: replace EnsureSuccessStatusCode() at both call sites with a small helper that reads the response body and embeds it in the thrown HttpRequestException. The VM's existing catch (Exception) in ExecuteAsync forwards ex.Message to the status bar, so the next 'click Save' tells the user exactly which field the server rejected. 2. Yavsc.Blogs.Tests: two integration tests on the real controller (no HTTP mock) — one pins that a well-formed PostIt-shaped payload (Title + Article + AuthorId + dates, Id=0) is accepted with 201, the other pins that a payload with Title=string.Empty is rejected with 400. Together they pin the contract the VM has to honour. 3. PostIt.Tests: a red [AvaloniaFact] UI test that mounts MainPage inside a headless Window, types a title into the TextBox without first selecting a post in the list, taps Save, and asserts the body of the first POST contains the typed title. Today this test fails with Title='', reproducing the production 400. The matching fix (a Title/Article buffer on MainPageViewModel that the XAML binds to, and that Save uses to build the outgoing BlogPost) is the next commit; the test is the safety net.
2026-07-11 02:52:50 +01:00
// (CS8639: "typeof cannot be used on a nullable reference
// type").
if (typeof(T) == typeof(BlogPostDto))
return Task.FromResult((T)(object)new BlogPostDto
PostIt/Yavsc.Blogs: surface 4xx body + pin controller + red UI test for Save The 'Save' button in PostIt has been returning 400 from /api/v1/blog ever since the editor's title and article fields were re-bound to SelectedPost.Title / SelectedPost.Article. The user types into the editor, taps Save, the controller rejects with 'The Title field is required', and the PostIt status bar shows only the generic 'Response status code does not indicate success: 400' — no field name, no reason. Three pieces here make the regression diagnosable and pin a test for the fix: 1. YavscApiClient: replace EnsureSuccessStatusCode() at both call sites with a small helper that reads the response body and embeds it in the thrown HttpRequestException. The VM's existing catch (Exception) in ExecuteAsync forwards ex.Message to the status bar, so the next 'click Save' tells the user exactly which field the server rejected. 2. Yavsc.Blogs.Tests: two integration tests on the real controller (no HTTP mock) — one pins that a well-formed PostIt-shaped payload (Title + Article + AuthorId + dates, Id=0) is accepted with 201, the other pins that a payload with Title=string.Empty is rejected with 400. Together they pin the contract the VM has to honour. 3. PostIt.Tests: a red [AvaloniaFact] UI test that mounts MainPage inside a headless Window, types a title into the TextBox without first selecting a post in the list, taps Save, and asserts the body of the first POST contains the typed title. Today this test fails with Title='', reproducing the production 400. The matching fix (a Title/Article buffer on MainPageViewModel that the XAML binds to, and that Save uses to build the outgoing BlogPost) is the next commit; the test is the safety net.
2026-07-11 02:52:50 +01:00
{
Id = 42,
Title = "Mon premier billet",
AuthorId = "tester",
Article = "Contenu du billet de test.",
});
if (typeof(T) == typeof(List<BlogPostDto>))
return Task.FromResult((T)(object)new List<BlogPostDto>
PostIt/Yavsc.Blogs: surface 4xx body + pin controller + red UI test for Save The 'Save' button in PostIt has been returning 400 from /api/v1/blog ever since the editor's title and article fields were re-bound to SelectedPost.Title / SelectedPost.Article. The user types into the editor, taps Save, the controller rejects with 'The Title field is required', and the PostIt status bar shows only the generic 'Response status code does not indicate success: 400' — no field name, no reason. Three pieces here make the regression diagnosable and pin a test for the fix: 1. YavscApiClient: replace EnsureSuccessStatusCode() at both call sites with a small helper that reads the response body and embeds it in the thrown HttpRequestException. The VM's existing catch (Exception) in ExecuteAsync forwards ex.Message to the status bar, so the next 'click Save' tells the user exactly which field the server rejected. 2. Yavsc.Blogs.Tests: two integration tests on the real controller (no HTTP mock) — one pins that a well-formed PostIt-shaped payload (Title + Article + AuthorId + dates, Id=0) is accepted with 201, the other pins that a payload with Title=string.Empty is rejected with 400. Together they pin the contract the VM has to honour. 3. PostIt.Tests: a red [AvaloniaFact] UI test that mounts MainPage inside a headless Window, types a title into the TextBox without first selecting a post in the list, taps Save, and asserts the body of the first POST contains the typed title. Today this test fails with Title='', reproducing the production 400. The matching fix (a Title/Article buffer on MainPageViewModel that the XAML binds to, and that Save uses to build the outgoing BlogPost) is the next commit; the test is the safety net.
2026-07-11 02:52:50 +01:00
{
new() { Id = 42, Title = "Mon premier billet" }
});
return Task.FromResult(default(T)!);
}
}