yavsc/src/PostIt.Tests/PostItViewModelTests.cs

114 lines
4.1 KiB
C#
Raw Normal View History

2026-06-10 00:23:17 +01:00
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
using System.Threading;
2026-06-10 00:23:17 +01:00
using System.Threading.Tasks;
using PostIt.Models;
using PostIt.Services;
using PostIt.ViewModels;
using Xunit;
namespace PostIt;
2026-06-10 00:23:17 +01:00
public class PostItViewModelTests
{
[Fact]
public void SearchCommand_filters_posts_by_title_article_or_author()
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
// MainPageViewModel no longer owns a BlogApiClient instance by
// default; tests construct one with a fake YavscApiClient that
// throws on any call (we never call the API in this test).
var fakeApi = new ThrowingYavscApiClient();
var blog = new BlogApiClient(fakeApi);
var viewModel = new MainPageViewModel(blog);
2026-06-10 00:23:17 +01:00
viewModel.Posts.Add(new BlogPost { Id = 1, Title = "First post", Article = "Hello world", AuthorId = "alice" });
viewModel.Posts.Add(new BlogPost { Id = 2, Title = "Second post", Article = "Nothing here", AuthorId = "bob" });
viewModel.Posts.Add(new BlogPost { Id = 3, Title = "Third post", Article = "Search me", AuthorId = "carol" });
viewModel.SearchText = "search";
viewModel.SearchCommand.Execute(null);
Assert.Single(viewModel.FilteredPosts);
Assert.Equal(3, viewModel.FilteredPosts[0].Id);
viewModel.SearchText = "bob";
viewModel.SearchCommand.Execute(null);
Assert.Single(viewModel.FilteredPosts);
Assert.Equal(2, viewModel.FilteredPosts[0].Id);
}
[Fact]
public async Task BlogApiClient_GetPostsAsync_returns_posts_from_api()
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
// The new BlogApiClient delegates transport to YavscApiClient.
// We feed it a fake YavscApiClient that returns the expected
// list straight from CallAsync.
2026-06-10 00:23:17 +01:00
var expected = new List<BlogPost>
{
new() { Id = 1, Title = "Hello" },
new() { Id = 2, Title = "World" }
};
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
var api = new StubYavscApiClient(expected);
var blog = new BlogApiClient(api);
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
var posts = await blog.GetPostsAsync();
2026-06-10 00:23:17 +01:00
Assert.Equal(2, posts.Count);
Assert.Equal("Hello", posts[0].Title);
}
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
/// <summary>Test fake that always throws if the API is invoked.</summary>
private sealed class ThrowingYavscApiClient : YavscApiClient
2026-06-10 00:23:17 +01:00
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public ThrowingYavscApiClient() : base(
new Settings
{
Scopes = new[] { "openid" },
Authentication = new AuthenticationSettings
{
Authority = "https://stub.invalid",
ClientId = "stub",
},
},
new TokenStore(System.IO.Path.GetTempFileName()))
{ }
public override Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
=> throw new System.InvalidOperationException("ThrowingYavscApiClient: API not stubbed.");
}
2026-06-10 00:23:17 +01:00
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
/// <summary>Test fake that hands back a canned list of posts from any CallAsync.</summary>
private sealed class StubYavscApiClient : YavscApiClient
{
private readonly List<BlogPost> _posts;
public StubYavscApiClient(List<BlogPost> posts)
: base(
new Settings
{
Scopes = new[] { "openid" },
Authentication = new AuthenticationSettings
{
Authority = "https://stub.invalid",
ClientId = "stub",
},
},
new TokenStore(System.IO.Path.GetTempFileName()))
2026-06-10 00:23:17 +01:00
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
_posts = posts;
2026-06-10 00:23:17 +01:00
}
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
public override Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
2026-06-10 00:23:17 +01:00
{
postit: wire BlogApiClient through YavscApiClient and unify auth BlogApiClient is a thin DTO↔path mapper on top of YavscApiClient: - No more HttpClient, no more accessToken constructor argument. - Single responsibility: turn Yavsc.Blogs endpoint paths into typed BlogPost payloads and back, while YavscApiClient owns auth + refresh + JSON shape. - Default path prefix is 'api/blog', overridable for tests. MainPageViewModel and App.axaml.cs are now free of any direct OidcClient / IBrowser / BearerToken plumbing. The MainPage receives a fully-configured BlogApiClient (which holds a YavscApiClient, which holds a TokenStore) at construction. The duplicate LoginAsync method on MainPageViewModel is gone; the LoginPage is the single entry point for the interactive PKCE flow. PlatformBootstrap.Desktop no longer overrides the redirect URI to loopback. PostIt runs the postit://callback custom scheme (SingleInstance hand-off) as the production path on desktop; the loopback constant stays for tests and for platforms that cannot register a custom scheme. Settings.cs: DefaultLoopbackRedirectUri is now documented as a fallback; DefaultDesktopRedirectUri ('postit://callback') is introduced as the canonical desktop default. TokenStore.Load() tolerates an empty file (returns null) so first-launch races and stubbed test fixtures don't blow up the constructor. YavscApiClient: - HasValidSession is exposed for warm-start UI logic. - CurrentAccessToken / CurrentIdToken are exposed so the LoginPage ViewModel can mirror the result onto its observable properties. - CallAsync<T> is now virtual (and the class is no longer sealed) to allow stubbing in PostItViewModelTests. Tests (PostIt.Tests): - YavscApiClientTests covers the silent refresh path (cache the token, mark it expired, observe a new Bearer in the API server), the 401 → refresh → retry path (forceFirstRequest on the stub), HasValidSession after login, and the throw-when-no-token guard. - OidcStubAuthority now mints a refresh_token in the token response so YavscApiClient.RefreshTokenAsync can hit /connect/token in tests. - PostItViewModelTests uses a ThrowingYavscApiClient / StubYavscApiClient pair instead of the old HttpClient injection point, matching the new constructor shape. dotnet test: 21/21 green. dotnet build: 0 errors.
2026-06-23 21:25:17 +01:00
// The canned fake only knows about a list of posts; the
// BlogApiClient test asserts on that list directly.
if (typeof(T) == typeof(List<BlogPost>))
return Task.FromResult((T)(object)_posts);
return Task.FromResult(default(T)!);
2026-06-10 00:23:17 +01:00
}
}
}