yavsc/src/PostIt.Tests/Blogs/MainPageSaveTests.cs

91 lines
3.6 KiB
C#
Raw Normal View History

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 Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.VisualTree;
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
using Microsoft.Extensions.DependencyInjection;
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;
using PostIt.Views;
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
using Yavsc.Api.Client;
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
namespace PostIt.Tests;
/// <summary>
/// Headless UI tests for the "Save" flow in <see cref="MainPage"/>.
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
/// Uses the shared <see cref="PostItHeadlessFixture"/> (a real
/// <see cref="MainWindow"/> with the production DI graph attached
/// to <see cref="App"/>) plus a local
/// <see cref="ServiceCollection"/> that swaps
/// <see cref="YavscApiClient"/> for the recording fake.
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
///
/// <para>The bug we are pinning: the title <c>TextBox</c> is
/// currently <c>{Binding SelectedPost.Title, Mode=TwoWay}</c>.
/// When <c>SelectedPost is null</c> (i.e. the user has not yet
/// clicked an item in the posts list — which is the only state
/// in which a brand-new post can be created), the binding has
/// no target and the user's keystrokes are silently dropped.
/// Clicking "Save" then routes to the VM branch
/// <c>if (SelectedPost is null) { new BlogPostDto { Title = string.Empty, ... } }</c>
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
/// which the controller rejects with 400 "The Title field is
/// required." This test fails on that branch today and will
/// pass once the VM owns a dedicated <c>Title</c>/<c>Article</c>
/// buffer that the XAML binds to and the Save command consumes.</para>
/// </summary>
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
[Collection("PostIt Headless")]
public sealed class MainPageSaveTests
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
{
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
private readonly PostItHeadlessCollection _host;
public MainPageSaveTests(PostItHeadlessCollection host)
{
_host = host;
}
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
[AvaloniaFact]
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
public void Typing_a_title_then_clicking_Save_sends_that_title_in_the_post_body()
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
{
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
// Arrange: VM with a recording API client, mounted on
// the shared MainWindow's nav stack.
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
var recorder = new CallRecorder();
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
var blog = _host.Services.GetRequiredService<BlogApiClient>();
var viewModel = new MainPageViewModel(blog);
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
var page = new MainPage { DataContext = viewModel };
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
_host.PushAsync(page);
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
// Act: type a title into the editor's TextBox without
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
// first selecting a post in the list — the only state
// in which a new post can be created. Then click Save.
var titleBox = _host.Window.GetVisualDescendants()
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
.OfType<TextBox>()
.First(t => t.PlaceholderText == "Title");
const string typed = "Mon premier billet";
titleBox.Text = typed;
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
var saveButton = _host.Window.GetVisualDescendants()
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
.OfType<Button>()
.Single(b => b.Content as string == "Save");
saveButton.Command!.Execute(null);
// The Save command is async (RelayCommand over Task) but
// ExecuteAsync would await; the sync Execute enqueues the
// task on the dispatcher. Give the dispatcher a chance to
// run so the awaited CallAsync has actually fired before
// we inspect the recorder.
feat(postit): circles+ACL UI, blog fixture→SQLite, seed default user Bundled end-of-branch commit on feat/postit-acl-members. PostIt UI for circles + per-post ACL - Reorganise PostIt.Tests into Auth/ and Blogs/ subfolders (Bearer/OIDC scope tests vs. blog API fakes live where they belong) and introduces PostItHeadlessCollection so the Avalonia.Headless tests share a single xUnit collection instead of contending with the EF-Core test host. - Adds BlogAclApiTests (a brand-new behavioural layer over POST /api/v1/blogacl) and the fakes it relies on (BlogApiTestFakes, BlogPostAuthorDtoTests, AddCircleMember DialogTests); pulls UserId-through-OIDC-sub path into BearerScopeTests / FakeAuthorizingBrowser / OidcStubAuthority. - App.axaml.cs gets a small PushPageAsync touch-up the new tests rely on. - Drops UnitTest1.cs (xUnit scaffold, never used). Yavsc.Blogs.Tests — SQLite instead of InMemory - Bumps Yavsc.Blogs.Tests.csproj on Microsoft.EntityFrameworkCore.Sqlite and rewrites BlogsWebServerFixture to hold a single shared SqliteConnection (Cache=Shared) for the fixture lifetime, with a sync Dispose close to dodge async teardown hangs. Reason: the EF Core InMemory provider silently ignores FKs, which masked the kind of bug we are about to pin in the ACL tests. SQLite enforces them, so any future INSERT that forgets to seed its parent rows fails loudly here instead of passing the test and breaking prod. - PublishEndpointTests and BlogApiSmokeTests get a one-line tweak to follow the new connection lifecycle. Foreign-key fallout: seed the default user in the fixture - Adds BlogsWebServerFixture.SeedUser(userName). Now that SQLite enforces BlogPost.AuthorId → AspNetUsers.Id, every test that POST/PUT/DELETE a BlogPost and sends AuthorId= 'tester' in the payload needs an AspNetUsers row to satisfy the FK or it returns 500 with SQLite Error 19. - BlogApiTests wraps the existing ResetDatabase with a ResetAndSeedDefaultUser helper for the six mutating tests; the four GET-only and ModelState-only tests keep the bare ResetDatabase. - Side benefit: every test in Yavsc.Blogs.Tests now finishes cleanly instead of hanging at teardown — previously a stuck test held the shared SqliteConnection open and the next tests waited indefinitely. Verified: dotnet test src/Yavsc.Blogs.Tests passes 25/25 green from a clean run, no fixture teardown hang.
2026-08-20 23:59:21 +01:00
var deadline = DateTime.UtcNow.AddSeconds(2);
while (recorder.Calls.Count == 0 && DateTime.UtcNow < deadline)
{
Task.Delay(20).GetAwaiter().GetResult();
}
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
// Assert: the first POST to "blog" carried a 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
// whose Title is exactly what the user typed. The bug
// fails this assertion with Title == string.Empty.
Assert.NotEmpty(recorder.Calls);
var (method, path, body) = recorder.FirstCall;
Assert.Equal(HttpMethod.Post, method);
Assert.Equal("blog", path);
var sent = Assert.IsType<BlogPostDto>(body);
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
Assert.Equal(typed, sent.Title);
}
}