From 00be48d3589751ed1799e257aa0f727cb717daa3 Mon Sep 17 00:00:00 2001 From: Jason Barden Date: Wed, 27 May 2026 12:13:39 +0100 Subject: [PATCH 1/2] test(issue-33): add failing RED tests for sync rule paths and account restore - GivenAnAccountOnboardingServiceIntegration: two tests asserting RemotePath is slash-prefixed folder name (e.g. /Documents), not a Graph item ID - GivenAWorkspaceViewModel: three tests asserting LoadPersistedAccountsAsync populates Accounts from repository, auto-selects first account, and leaves collection empty when repository returns no rows Supporting stubs (not yet implemented): - SelectedFolder readonly record struct added to Domain - OneDriveAccount.SelectedFolders property alongside SelectedFolderIds - WorkspaceViewModel(IServiceProvider, IAccountRepository) constructor overload - WorkspaceViewModel.LoadPersistedAccountsAsync stub (throws NotImplementedException) Co-Authored-By: Claude Sonnet 4.6 --- .../Domain/OneDriveAccount.cs | 5 +- .../Domain/SelectedFolder.cs | 6 ++ .../Workspace/WorkspaceViewModel.cs | 18 ++++++ ...enAnAccountOnboardingServiceIntegration.cs | 34 ++++++++++ .../Workspace/GivenAWorkspaceViewModel.cs | 63 +++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/AStar.Dev.CloudSyncFunctional/Domain/SelectedFolder.cs diff --git a/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs b/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs index cf179c0..11af2ba 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs @@ -17,6 +17,9 @@ public sealed class OneDriveAccount /// Gets the Graph drive ID for this account's OneDrive. public string? DriveId { get; init; } - /// Gets the IDs of folders the user selected for sync. + /// Gets the folders selected for sync, carrying both Graph item ID and display name. + public IReadOnlyList SelectedFolders { get; init; } = []; + + /// Gets the IDs of folders selected for sync. public IReadOnlyList SelectedFolderIds { get; init; } = []; } diff --git a/src/AStar.Dev.CloudSyncFunctional/Domain/SelectedFolder.cs b/src/AStar.Dev.CloudSyncFunctional/Domain/SelectedFolder.cs new file mode 100644 index 0000000..28fd781 --- /dev/null +++ b/src/AStar.Dev.CloudSyncFunctional/Domain/SelectedFolder.cs @@ -0,0 +1,6 @@ +namespace AStar.Dev.CloudSyncFunctional.Domain; + +/// A OneDrive root folder selected for sync, carrying both the Graph item ID and the display name. +/// The Graph drive item identifier. +/// The folder display name (e.g. "Documents"). +public readonly record struct SelectedFolder(string Id, string Name); diff --git a/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs b/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs index 00510cb..7add9bd 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs @@ -2,6 +2,8 @@ using AStar.Dev.CloudSyncFunctional.Accounts; using AStar.Dev.CloudSyncFunctional.Domain; using AStar.Dev.CloudSyncFunctional.FolderTree; +using AStar.Dev.CloudSyncFunctional.Persistence.Entities; +using AStar.Dev.CloudSyncFunctional.Persistence.Repositories; using AStar.Dev.CloudSyncFunctional.Wizard; using Microsoft.Extensions.DependencyInjection; using ReactiveUI; @@ -13,6 +15,7 @@ namespace AStar.Dev.CloudSyncFunctional.Workspace; public class WorkspaceViewModel : ReactiveObject { private readonly IServiceProvider _serviceProvider; + private readonly IAccountRepository? _accountRepository; /// Gets all cloud storage accounts registered in the workspace. public ObservableCollection Accounts { get; } = BuildAccounts(); @@ -71,6 +74,21 @@ public ReactiveObject? CurrentOverlay /// Gets a formatted subtitle summarising account count and total storage capacity. public string WorkspaceSubtitle => $"{Accounts.Count} accounts · {Accounts.Sum(a => a.TotalBytes) / 1_099_511_627_776.0:F1} TB total"; + /// Initialises a new using the provided service provider and account repository. + /// The DI container used to resolve the wizard ViewModel on demand. + /// Repository used to load persisted accounts on startup. + public WorkspaceViewModel(IServiceProvider serviceProvider, IAccountRepository accountRepository) + : this(serviceProvider) + { + _accountRepository = accountRepository; + } + + /// Loads persisted accounts from the database and populates . + /// Cancellation token. + /// A task that completes when accounts are loaded and added to the collection. + public Task LoadPersistedAccountsAsync(CancellationToken ct = default) + => throw new NotImplementedException(); + /// Initialises a new using the provided service provider. /// The DI container used to resolve the wizard ViewModel on demand. public WorkspaceViewModel(IServiceProvider serviceProvider) diff --git a/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs b/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs index 41f5c59..e6a9a0d 100644 --- a/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs +++ b/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs @@ -76,4 +76,38 @@ public async Task when_complete_onboarding_is_called_with_no_folders_then_no_syn rules.ShouldBeEmpty(); } + + [Fact] + public async Task when_complete_onboarding_is_called_then_sync_rule_remote_path_is_slash_prefixed_folder_name() + { + var account = new OneDriveAccount + { + AccountId = Guid.NewGuid().ToString(), + Profile = new AccountProfile("Test User", "test@example.com"), + SelectedFolders = [new SelectedFolder("graph-item-id-abc123", "Documents")] + }; + var sut = CreateSut(); + + await sut.CompleteOnboardingAsync(account, CancellationToken.None); + var rules = await CreateSyncRuleRepo().GetByAccountAsync(new AccountId(account.AccountId), CancellationToken.None); + + rules[0].RemotePath.ShouldBe("/Documents"); + } + + [Fact] + public async Task when_complete_onboarding_is_called_then_sync_rule_remote_path_does_not_contain_graph_item_id() + { + var account = new OneDriveAccount + { + AccountId = Guid.NewGuid().ToString(), + Profile = new AccountProfile("Test User", "test@example.com"), + SelectedFolders = [new SelectedFolder("graph-item-id-abc123", "Pictures")] + }; + var sut = CreateSut(); + + await sut.CompleteOnboardingAsync(account, CancellationToken.None); + var rules = await CreateSyncRuleRepo().GetByAccountAsync(new AccountId(account.AccountId), CancellationToken.None); + + rules[0].RemotePath.ShouldNotContain("graph-item-id-abc123"); + } } diff --git a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs index a168f12..4c9dc03 100644 --- a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs +++ b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs @@ -3,6 +3,9 @@ using AStar.Dev.CloudSyncFunctional.Domain; using AStar.Dev.CloudSyncFunctional.Graph; using AStar.Dev.CloudSyncFunctional.Onboarding; +using AStar.Dev.CloudSyncFunctional.Persistence.Entities; +using AStar.Dev.CloudSyncFunctional.Persistence.Repositories; +using AStar.Dev.CloudSyncFunctional.Persistence.ValueObjects; using AStar.Dev.CloudSyncFunctional.Tests.Unit.Infrastructure; using AStar.Dev.CloudSyncFunctional.Wizard; using AStar.Dev.CloudSyncFunctional.Workspace; @@ -247,4 +250,64 @@ public void when_wizard_completed_then_account_is_added_to_accounts() sut.Accounts.Count.ShouldBe(5); } + + [Fact] + public async Task when_load_persisted_accounts_is_called_then_stored_account_appears_in_accounts() + { + var accountRepo = Substitute.For(); + accountRepo.GetAllAsync(Arg.Any()) + .Returns(Task.FromResult>( + [ + new AccountEntity + { + Id = new AccountId("acc-1"), + Profile = new AccountProfileEntity { DisplayName = new DisplayName("Alice"), Email = new EmailAddress("alice@x.com") }, + IsActive = true, + DriveId = new DriveId("drive-1"), + SyncConfig = new AccountSyncConfig { LocalSyncPath = new LocalSyncPath("/home/alice/OneDrive"), WorkerCount = 8 } + } + ])); + var sut = new WorkspaceViewModel(new ServiceCollection().BuildServiceProvider(), accountRepo); + + await sut.LoadPersistedAccountsAsync(CancellationToken.None); + + sut.Accounts.ShouldContain(a => a.Email == "alice@x.com"); + } + + [Fact] + public async Task when_load_persisted_accounts_is_called_with_no_accounts_then_accounts_collection_is_empty() + { + var accountRepo = Substitute.For(); + accountRepo.GetAllAsync(Arg.Any()) + .Returns(Task.FromResult>([])); + var sut = new WorkspaceViewModel(new ServiceCollection().BuildServiceProvider(), accountRepo); + + await sut.LoadPersistedAccountsAsync(CancellationToken.None); + + sut.Accounts.ShouldBeEmpty(); + } + + [Fact] + public async Task when_load_persisted_accounts_is_called_then_first_account_is_auto_selected() + { + var accountRepo = Substitute.For(); + accountRepo.GetAllAsync(Arg.Any()) + .Returns(Task.FromResult>( + [ + new AccountEntity + { + Id = new AccountId("acc-1"), + Profile = new AccountProfileEntity { DisplayName = new DisplayName("Bob"), Email = new EmailAddress("bob@x.com") }, + IsActive = true, + DriveId = new DriveId("drive-1"), + SyncConfig = new AccountSyncConfig { LocalSyncPath = new LocalSyncPath("/home/bob/OneDrive"), WorkerCount = 8 } + } + ])); + var sut = new WorkspaceViewModel(new ServiceCollection().BuildServiceProvider(), accountRepo); + + await sut.LoadPersistedAccountsAsync(CancellationToken.None); + + sut.SelectedAccount.ShouldNotBeNull(); + sut.SelectedAccount!.Email.ShouldBe("bob@x.com"); + } } From 07db7f4b2a410fa076370d33e3ff3fb61613de67 Mon Sep 17 00:00:00 2001 From: Jason Barden Date: Wed, 27 May 2026 12:19:12 +0100 Subject: [PATCH 2/2] fix(issue-33): account restore on restart + sync rule paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 — accounts not restored on app restart: - WorkspaceViewModel now has a two-arg constructor (IServiceProvider, IAccountRepository) that starts with an empty Accounts collection (no design-time data on the runtime path) - LoadPersistedAccountsAsync loads all AccountEntity rows from IAccountRepository, maps them to AccountViewModel, adds to Accounts, and auto-selects the first account - App.axaml.cs resolves the WorkspaceViewModel (DI now injects IAccountRepository) and fires LoadPersistedAccountsAsync after the main window is created - Design-time / single-IServiceProvider constructor retains BuildAccounts() fake data unchanged Bug 2 — sync rules storing Graph item IDs not folder paths: - OneDriveAccount.SelectedFolderIds removed; replaced by SelectedFolders: IReadOnlyList - SelectedFolder is a new readonly record struct(string Id, string Name) carrying both the Graph drive item ID and the display name - AddAccountWizardViewModel.ExecuteAddAccountAsync now builds SelectedFolder instances from WizardFolderItem.FolderId + Name - AccountOnboardingService.UpsertSyncRulesAsync now writes RemotePath = "/\{folder.Name}" (e.g. "/Documents") instead of the raw Graph item ID - WorkspaceViewModel.OnWizardCompleted uses SelectedFolders.Count All existing tests updated to use SelectedFolders. 197 tests green. Co-Authored-By: Claude Sonnet 4.6 --- .../App.axaml.cs | 6 ++- .../Domain/OneDriveAccount.cs | 3 -- .../Onboarding/AccountOnboardingService.cs | 4 +- .../Wizard/AddAccountWizardViewModel.cs | 2 +- .../Workspace/WorkspaceViewModel.cs | 37 +++++++++++++++---- ...enAnAccountOnboardingServiceIntegration.cs | 4 +- .../GivenAnAccountOnboardingService.cs | 2 +- .../Workspace/GivenAWorkspaceViewModel.cs | 4 +- 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/AStar.Dev.CloudSyncFunctional/App.axaml.cs b/src/AStar.Dev.CloudSyncFunctional/App.axaml.cs index 1fd4893..8b80004 100644 --- a/src/AStar.Dev.CloudSyncFunctional/App.axaml.cs +++ b/src/AStar.Dev.CloudSyncFunctional/App.axaml.cs @@ -40,7 +40,11 @@ public override void OnFrameworkInitializationCompleted() ApplyDatabaseMigrations(_serviceProvider); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - desktop.MainWindow = new MainWindow(_serviceProvider.GetRequiredService()); + { + var viewModel = _serviceProvider.GetRequiredService(); + desktop.MainWindow = new MainWindow(viewModel); + _ = viewModel.LoadPersistedAccountsAsync(CancellationToken.None); + } base.OnFrameworkInitializationCompleted(); } diff --git a/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs b/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs index 11af2ba..98718c0 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Domain/OneDriveAccount.cs @@ -19,7 +19,4 @@ public sealed class OneDriveAccount /// Gets the folders selected for sync, carrying both Graph item ID and display name. public IReadOnlyList SelectedFolders { get; init; } = []; - - /// Gets the IDs of folders selected for sync. - public IReadOnlyList SelectedFolderIds { get; init; } = []; } diff --git a/src/AStar.Dev.CloudSyncFunctional/Onboarding/AccountOnboardingService.cs b/src/AStar.Dev.CloudSyncFunctional/Onboarding/AccountOnboardingService.cs index bd9d684..9fa38a4 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Onboarding/AccountOnboardingService.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Onboarding/AccountOnboardingService.cs @@ -36,13 +36,13 @@ public async Task> CompleteOnboardingA private async Task> UpsertSyncRulesAsync(OneDriveAccount account, CancellationToken ct) { - foreach (var folderId in account.SelectedFolderIds) + foreach (var folder in account.SelectedFolders) { var rule = new SyncRuleEntity { Id = new SyncRuleId(Guid.NewGuid().ToString()), AccountId = new AccountId(account.AccountId), - RemotePath = folderId, + RemotePath = $"/{folder.Name}", RuleType = RuleType.Include }; diff --git a/src/AStar.Dev.CloudSyncFunctional/Wizard/AddAccountWizardViewModel.cs b/src/AStar.Dev.CloudSyncFunctional/Wizard/AddAccountWizardViewModel.cs index 6cb440a..3e67b91 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Wizard/AddAccountWizardViewModel.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Wizard/AddAccountWizardViewModel.cs @@ -275,7 +275,7 @@ private async Task ExecuteAddAccountAsync(CancellationToken ct) { AccountId = _authResult.AccountId, Profile = _authResult.Profile, - SelectedFolderIds = Folders.Where(f => f.IsSelected).Select(f => f.FolderId).ToList() + SelectedFolders = Folders.Where(f => f.IsSelected).Select(f => new SelectedFolder(f.FolderId, f.Name)).ToList() }; await _onboardingService.CompleteOnboardingAsync(account, ct) diff --git a/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs b/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs index 7add9bd..d3f2f39 100644 --- a/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs +++ b/src/AStar.Dev.CloudSyncFunctional/Workspace/WorkspaceViewModel.cs @@ -18,7 +18,7 @@ public class WorkspaceViewModel : ReactiveObject private readonly IAccountRepository? _accountRepository; /// Gets all cloud storage accounts registered in the workspace. - public ObservableCollection Accounts { get; } = BuildAccounts(); + public ObservableCollection Accounts { get; } /// Gets or sets the currently selected account. public AccountViewModel? SelectedAccount @@ -74,31 +74,43 @@ public ReactiveObject? CurrentOverlay /// Gets a formatted subtitle summarising account count and total storage capacity. public string WorkspaceSubtitle => $"{Accounts.Count} accounts · {Accounts.Sum(a => a.TotalBytes) / 1_099_511_627_776.0:F1} TB total"; - /// Initialises a new using the provided service provider and account repository. + /// Initialises a new using the provided service provider and account repository (runtime path). /// The DI container used to resolve the wizard ViewModel on demand. /// Repository used to load persisted accounts on startup. public WorkspaceViewModel(IServiceProvider serviceProvider, IAccountRepository accountRepository) - : this(serviceProvider) { + _serviceProvider = serviceProvider; _accountRepository = accountRepository; + Accounts = []; + OpenAddAccountWizard = ReactiveCommand.Create(ExecuteOpenAddAccountWizard); } /// Loads persisted accounts from the database and populates . /// Cancellation token. /// A task that completes when accounts are loaded and added to the collection. - public Task LoadPersistedAccountsAsync(CancellationToken ct = default) - => throw new NotImplementedException(); + public async Task LoadPersistedAccountsAsync(CancellationToken ct = default) + { + if (_accountRepository is null) + return; + + var entities = await _accountRepository.GetAllAsync(ct); + foreach (var vm in entities.Select(MapToViewModel)) + Accounts.Add(vm); + if (Accounts.Count > 0 && SelectedAccount is null) + SelectedAccount = Accounts[0]; + } - /// Initialises a new using the provided service provider. + /// Initialises a new using the provided service provider (design-time and test use). /// The DI container used to resolve the wizard ViewModel on demand. public WorkspaceViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; + Accounts = BuildAccounts(); SelectedAccount = Accounts[0]; OpenAddAccountWizard = ReactiveCommand.Create(ExecuteOpenAddAccountWizard); } - /// Initialises a new with no DI services (design-time and test use). + /// Initialises a new with no DI services (design-time use). public WorkspaceViewModel() : this(EmptyServiceProvider.Instance) { } @@ -121,12 +133,21 @@ private void OnWizardCompleted(object? sender, OneDriveAccount account) Name = account.Profile.DisplayName, Email = account.Profile.Email, Status = SyncStatus.Ok, - FolderCount = account.SelectedFolderIds.Count + FolderCount = account.SelectedFolders.Count }); SelectedAccount = Accounts[^1]; this.RaisePropertyChanged(nameof(WorkspaceSubtitle)); } + private static AccountViewModel MapToViewModel(AccountEntity entity) => + new() + { + Kind = ProviderKind.OneDrive, + Name = entity.Profile.DisplayName.Value, + Email = entity.Profile.Email.Value, + Status = SyncStatus.Ok + }; + private void OnWizardCancelled(object? sender, EventArgs e) { DetachAndDisposeWizard(sender); diff --git a/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs b/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs index e6a9a0d..7c1969d 100644 --- a/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs +++ b/test/AStar.Dev.CloudSyncFunctional.Tests.Integration/Onboarding/GivenAnAccountOnboardingServiceIntegration.cs @@ -22,12 +22,12 @@ private SyncRuleRepository CreateSyncRuleRepo() => private AccountRepository CreateAccountRepo() => new(new TestDbContextFactory(db.Connection)); - private static OneDriveAccount CreateAccount(params string[] folderIds) => + private static OneDriveAccount CreateAccount(params string[] folderNames) => new() { AccountId = Guid.NewGuid().ToString(), Profile = new AccountProfile("Test User", "test@example.com"), - SelectedFolderIds = folderIds + SelectedFolders = folderNames.Select((name, i) => new SelectedFolder($"graph-id-{i}", name)).ToList() }; [Fact] diff --git a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Onboarding/GivenAnAccountOnboardingService.cs b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Onboarding/GivenAnAccountOnboardingService.cs index e453317..e1148b4 100644 --- a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Onboarding/GivenAnAccountOnboardingService.cs +++ b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Onboarding/GivenAnAccountOnboardingService.cs @@ -29,7 +29,7 @@ private static OneDriveAccount CreateAccount() => { AccountId = "test-account-id", Profile = new AccountProfile("Test User", "test@example.com"), - SelectedFolderIds = ["folder-1", "folder-2"] + SelectedFolders = [new SelectedFolder("folder-1-id", "folder-1"), new SelectedFolder("folder-2-id", "folder-2")] }; [Fact] diff --git a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs index 4c9dc03..c0cfe33 100644 --- a/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs +++ b/test/AStar.Dev.CloudSyncFunctional.Tests.Unit/Workspace/GivenAWorkspaceViewModel.cs @@ -213,7 +213,7 @@ public void when_wizard_completed_then_current_overlay_is_null() var auth = Substitute.For(); var graph = Substitute.For(); var onboarding = Substitute.For(); - var account = new OneDriveAccount { AccountId = "id", Profile = new AccountProfile("Name", "email@x.com"), SelectedFolderIds = [] }; + var account = new OneDriveAccount { AccountId = "id", Profile = new AccountProfile("Name", "email@x.com"), SelectedFolders = [] }; onboarding.CompleteOnboardingAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult>(new Ok(account))); @@ -235,7 +235,7 @@ public void when_wizard_completed_then_account_is_added_to_accounts() var auth = Substitute.For(); var graph = Substitute.For(); var onboarding = Substitute.For(); - var account = new OneDriveAccount { AccountId = "id", Profile = new AccountProfile("New User", "new@x.com"), SelectedFolderIds = ["f1"] }; + var account = new OneDriveAccount { AccountId = "id", Profile = new AccountProfile("New User", "new@x.com"), SelectedFolders = [new SelectedFolder("f1-id", "f1")] }; onboarding.CompleteOnboardingAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult>(new Ok(account)));