diff --git a/src/Core/Storage/AppPaths.cs b/src/Core/Storage/AppPaths.cs
index 2d1e474..3a69cf2 100644
--- a/src/Core/Storage/AppPaths.cs
+++ b/src/Core/Storage/AppPaths.cs
@@ -21,6 +21,13 @@ public static class AppPaths
/// Explicit override of the data root (e.g. CLI --data-dir). Takes priority over everything.
public static string? OverrideDataRoot { get; set; }
+ // Test seams: redirect the machine-global bootstrap locations (APPDATA pointer
+ // dir, executable dir, per-user default root) to a sandbox so the resolution
+ // precedence can be exercised without touching real user data.
+ internal static string? BootstrapDirOverride { get; set; }
+ internal static string? PortableBaseOverride { get; set; }
+ internal static string? DefaultRootOverride { get; set; }
+
///
/// Default data root, following each platform's convention:
/// Windows → %APPDATA%\PalladiumWallet (PascalCase, like Electrum/Bitcoin);
@@ -29,6 +36,8 @@ public static class AppPaths
///
public static string DefaultDataRoot()
{
+ if (DefaultRootOverride is { } o)
+ return o;
if (OperatingSystem.IsWindows())
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
@@ -41,11 +50,12 @@ public static class AppPaths
/// bootstrap location that is always writable and independent of the data root.
private static string LocationPointerPath() =>
Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- AppDirName, "data-location");
+ BootstrapDirOverride ?? Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDirName),
+ "data-location");
private static string PortableRoot() =>
- Path.Combine(AppContext.BaseDirectory, PortableDirName);
+ Path.Combine(PortableBaseOverride ?? AppContext.BaseDirectory, PortableDirName);
private static bool HasData(string root) =>
Directory.Exists(root) && Directory.EnumerateFileSystemEntries(root).Any();
diff --git a/tests/PalladiumWallet.Tests/Storage/AppPathsTests.cs b/tests/PalladiumWallet.Tests/Storage/AppPathsTests.cs
index b5e3be1..731a1d6 100644
--- a/tests/PalladiumWallet.Tests/Storage/AppPathsTests.cs
+++ b/tests/PalladiumWallet.Tests/Storage/AppPathsTests.cs
@@ -7,9 +7,11 @@ namespace PalladiumWallet.Tests.Storage;
/// Tests for the data-path resolution (§8), pinned to a temporary root via
/// — the same seam the Android head and
/// the CLI --data-dir use — so nothing outside the temp folder is touched.
-/// The pointer-file and portable-mode branches read machine-global locations
-/// and are deliberately not exercised here.
+/// The pointer/portable/default precedence has its own sandboxed tests in
+/// ; both classes share a collection
+/// because AppPaths state is static.
///
+[Collection("AppPaths")]
public class AppPathsTests : IDisposable
{
private readonly string _root;
@@ -82,3 +84,98 @@ public class AppPathsTests : IDisposable
Assert.NotEqual(AppPaths.WalletsDir(NetKind.Testnet), AppPaths.WalletsDir(NetKind.Regtest));
}
}
+
+///
+/// Precedence tests for — override → portable →
+/// pointer → default — using the internal bootstrap seams to sandbox the
+/// machine-global locations (APPDATA pointer dir, executable dir, default root).
+///
+[Collection("AppPaths")]
+public sealed class AppPathsResolutionTests : IDisposable
+{
+ private readonly string _sandbox;
+ private readonly string? _savedOverride;
+
+ public AppPathsResolutionTests()
+ {
+ _sandbox = Path.Combine(Path.GetTempPath(), $"plm-paths-{Guid.NewGuid()}");
+ Directory.CreateDirectory(_sandbox);
+ _savedOverride = AppPaths.OverrideDataRoot;
+ AppPaths.OverrideDataRoot = null;
+ AppPaths.BootstrapDirOverride = Path.Combine(_sandbox, "bootstrap");
+ AppPaths.PortableBaseOverride = Path.Combine(_sandbox, "exe");
+ AppPaths.DefaultRootOverride = Path.Combine(_sandbox, "default-root");
+ Directory.CreateDirectory(AppPaths.PortableBaseOverride);
+ }
+
+ public void Dispose()
+ {
+ AppPaths.OverrideDataRoot = _savedOverride;
+ AppPaths.BootstrapDirOverride = null;
+ AppPaths.PortableBaseOverride = null;
+ AppPaths.DefaultRootOverride = null;
+ if (Directory.Exists(_sandbox))
+ Directory.Delete(_sandbox, recursive: true);
+ }
+
+ private string PortableDir => Path.Combine(_sandbox, "exe", AppPaths.PortableDirName);
+
+ [Fact]
+ public void Senza_alcuna_configurazione_vince_il_default_e_la_posizione_non_e_configurata()
+ {
+ Assert.Equal(Path.Combine(_sandbox, "default-root"), AppPaths.DataRoot());
+ Assert.False(AppPaths.IsDataLocationConfigured());
+ }
+
+ [Fact]
+ public void La_cartella_portable_accanto_all_eseguibile_vince_sul_pointer_e_sul_default()
+ {
+ AppPaths.ConfigureDataLocation(Path.Combine(_sandbox, "custom"));
+ Directory.CreateDirectory(PortableDir);
+
+ Assert.Equal(PortableDir, AppPaths.DataRoot());
+ Assert.True(AppPaths.IsDataLocationConfigured());
+ }
+
+ [Fact]
+ public void Il_pointer_scritto_da_ConfigureDataLocation_vince_sul_default()
+ {
+ var custom = Path.Combine(_sandbox, "custom");
+ AppPaths.ConfigureDataLocation($" {custom} "); // trims and creates
+
+ Assert.True(Directory.Exists(custom));
+ Assert.Equal(custom, AppPaths.DataRoot());
+ Assert.True(AppPaths.IsDataLocationConfigured());
+ }
+
+ [Fact]
+ public void Un_pointer_vuoto_viene_ignorato_e_si_ricade_sul_default()
+ {
+ Directory.CreateDirectory(AppPaths.BootstrapDirOverride!);
+ File.WriteAllText(Path.Combine(AppPaths.BootstrapDirOverride!, "data-location"), " ");
+
+ Assert.Equal(Path.Combine(_sandbox, "default-root"), AppPaths.DataRoot());
+ Assert.False(AppPaths.IsDataLocationConfigured());
+ }
+
+ [Fact]
+ public void Dati_gia_presenti_nel_default_contano_come_posizione_configurata()
+ {
+ var defaultRoot = Path.Combine(_sandbox, "default-root");
+ Directory.CreateDirectory(defaultRoot);
+ Assert.False(AppPaths.IsDataLocationConfigured()); // exists but empty
+
+ File.WriteAllText(Path.Combine(defaultRoot, "config.json"), "{}");
+ Assert.True(AppPaths.IsDataLocationConfigured());
+ }
+
+ [Fact]
+ public void L_override_esplicito_vince_anche_su_portable_e_pointer()
+ {
+ Directory.CreateDirectory(PortableDir);
+ AppPaths.ConfigureDataLocation(Path.Combine(_sandbox, "custom"));
+ AppPaths.OverrideDataRoot = Path.Combine(_sandbox, "override");
+
+ Assert.Equal(Path.Combine(_sandbox, "override"), AppPaths.DataRoot());
+ }
+}