postit: point Register button at /Account/Register

/signin only renders the local-account sign-in form on Yavsc.Org;
new accounts live at /Account/Register. Update RegisterUrl in
LoginPageViewModel and the matching test accordingly.
This commit is contained in:
Paul Schneider 2026-06-21 03:03:56 +01:00
commit 9e190e3d3d
5 changed files with 98 additions and 13 deletions

View file

@ -60,7 +60,7 @@ public class LoginPageViewModelTests
// Trailing slash on Authority is normalised away.
Assert.Equal(
"https://yavsc.example.com/signin?ReturnUrl=~%2F&AllowRememberLogin=true",
"https://yavsc.example.com/Account/Register",
vm.RegisterUrl);
Assert.Equal(
"https://yavsc.example.com/Account/ForgotPassword",

View file

@ -0,0 +1,36 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Xunit;
namespace PostIt.Tests;
public class SettingsLoadTests
{
/// <summary>
/// On the dev machine, the user-level settings file
/// (~/.config/PostIt/postit-settings.json) does not exist, so Load()
/// must fall back to the embedded default resource shipped inside
/// PostIt.dll.
/// </summary>
[Fact]
public async Task Load_falls_back_to_embedded_resource_when_user_file_missing()
{
// Skip if a user-level file exists (CI / different dev machines).
var userConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"PostIt",
"postit-settings.json");
if (File.Exists(userConfigPath))
{
return; // nothing to assert: user file wins.
}
var settings = new PostIt.Settings();
await settings.Load();
// The bundled postit-settings.json points at yavsc.pschneider.fr.
Assert.False(string.IsNullOrWhiteSpace(settings.Authentication?.Authority));
Assert.Equal("postit", settings.Authentication.ClientId);
}
}

View file

@ -26,4 +26,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="postit-settings.json">
<LogicalName>PostIt.postit-settings.json</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Project>

View file

@ -1,3 +1,5 @@
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
@ -9,6 +11,8 @@ using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
[assembly: InternalsVisibleTo("PostIt.Tests")]
namespace PostIt;
public partial class Settings : ObservableObject
@ -87,7 +91,16 @@ public partial class Settings : ObservableObject
if (!configFileInfo.Exists)
{
Console.Error.WriteLine($"🩎 Settings file not found at {configFileInfo.FullName}");
return; // no settings file
// Only fall back to the embedded default when the in-memory
// settings haven't been populated yet. This protects callers
// (notably tests) that pre-load Settings with explicit values
// from being silently overwritten by the bundled default.
if (string.IsNullOrWhiteSpace(this.Authentication?.Authority)
&& !TryLoadEmbeddedFallback())
{
Console.Error.WriteLine("🩎 No embedded default settings; running with empty configuration.");
}
return; // no user settings file
}
Console.WriteLine($"🔎 Loading settings from {configFileInfo.FullName}");
@ -97,17 +110,49 @@ public partial class Settings : ObservableObject
using var stream = configFileInfo.OpenRead();
using var reader = new StreamReader(stream);
var json = await reader.ReadToEndAsync();
if (string.IsNullOrWhiteSpace(json))
{
Console.Error.WriteLine("🩎 Settings file is empty.");
return;
}
ApplyJson(json, $"user file {configFileInfo.FullName}");
}
catch (Exception ex)
{
Console.Error.WriteLine($"🩎 Error loading settings: {ex.Message}");
}
}
private bool TryLoadEmbeddedFallback()
{
const string ResourceName = "PostIt.postit-settings.json";
var assembly = typeof(Settings).Assembly;
using var stream = assembly.GetManifestResourceStream(ResourceName);
if (stream is null)
{
Console.Error.WriteLine($"🩎 Embedded resource {ResourceName} not found.");
return false;
}
using var reader = new StreamReader(stream);
var json = reader.ReadToEnd();
if (string.IsNullOrWhiteSpace(json))
{
Console.Error.WriteLine("🩎 Embedded settings resource is empty.");
return false;
}
Console.WriteLine($"🔎 Loading embedded default settings ({ResourceName}).");
ApplyJson(json, $"embedded resource {ResourceName}");
return true;
}
private void ApplyJson(string json, string source)
{
if (string.IsNullOrWhiteSpace(json))
{
Console.Error.WriteLine($"🩎 Settings payload is empty (source: {source}).");
return;
}
try
{
var settings = JsonSerializer.Deserialize<Settings>(json);
if (settings is null)
{
Console.Error.WriteLine("🩎 Settings file is invalid.");
Console.Error.WriteLine($"🩎 Settings payload is invalid (source: {source}).");
return;
}
this.Authentication = settings.Authentication;
@ -115,11 +160,10 @@ public partial class Settings : ObservableObject
this.ApiUrl = settings.ApiUrl;
this.RedirectUri = string.IsNullOrWhiteSpace(settings.RedirectUri) ? DefaultLoopbackRedirectUri : settings.RedirectUri;
this.Scopes = settings.Scopes;
}
catch (Exception ex)
{
Console.Error.WriteLine($"🩎 Error loading settings: {ex.Message}");
Console.Error.WriteLine($"🩎 Error applying settings from {source}: {ex.Message}");
}
}
}

View file

@ -13,12 +13,12 @@ public partial class LoginPageViewModel : ViewModelBase
public string Password { get; set; }
/// <summary>
/// URL of the Yavsc.Org register/sign-in page for new users.
/// URL of the Yavsc.Org account-registration page.
/// Derived from <see cref="Settings.Authentication"/>'s Authority.
/// Empty when the authority is not configured.
/// </summary>
public string RegisterUrl =>
BuildExternalUrl("/signin?ReturnUrl=~%2F&AllowRememberLogin=true");
BuildExternalUrl("/Account/Register");
/// <summary>
/// URL of the Yavsc.Org password-reset page (open to anonymous users).