diff --git a/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelLogoTests.cs b/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelLogoTests.cs index ee49ccf..192457b 100644 --- a/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelLogoTests.cs +++ b/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelLogoTests.cs @@ -15,6 +15,7 @@ public class ProfileManagementViewModelLogoTests : IDisposable { private readonly string _tempFile; private readonly Mock _mockBranding; + private readonly Mock _mockAppReg; private readonly GraphClientFactory _graphClientFactory; private readonly ILogger _logger; @@ -23,6 +24,7 @@ public class ProfileManagementViewModelLogoTests : IDisposable _tempFile = Path.GetTempFileName(); File.Delete(_tempFile); _mockBranding = new Mock(); + _mockAppReg = new Mock(); _graphClientFactory = new GraphClientFactory(new MsalClientFactory()); _logger = NullLogger.Instance; } @@ -40,7 +42,8 @@ public class ProfileManagementViewModelLogoTests : IDisposable profileService, _mockBranding.Object, _graphClientFactory, - _logger); + _logger, + _mockAppReg.Object); } [Fact] @@ -102,7 +105,8 @@ public class ProfileManagementViewModelLogoTests : IDisposable profileService, _mockBranding.Object, _graphClientFactory, - _logger); + _logger, + _mockAppReg.Object); vm.SelectedProfile = profile; @@ -173,7 +177,8 @@ public class ProfileManagementViewModelLogoTests : IDisposable profileService, _mockBranding.Object, _graphClientFactory, - _logger); + _logger, + _mockAppReg.Object); vm.SelectedProfile = profile; Assert.NotNull(vm.ClientLogoPreview); diff --git a/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelRegistrationTests.cs b/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelRegistrationTests.cs new file mode 100644 index 0000000..dcfb981 --- /dev/null +++ b/SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelRegistrationTests.cs @@ -0,0 +1,157 @@ +using System.IO; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using SharepointToolbox.Core.Models; +using SharepointToolbox.Infrastructure.Auth; +using SharepointToolbox.Infrastructure.Persistence; +using SharepointToolbox.Services; +using SharepointToolbox.ViewModels; + +namespace SharepointToolbox.Tests.ViewModels; + +[Trait("Category", "Unit")] +public class ProfileManagementViewModelRegistrationTests : IDisposable +{ + private readonly string _tempFile; + private readonly Mock _mockBranding; + private readonly Mock _mockAppReg; + private readonly GraphClientFactory _graphClientFactory; + + public ProfileManagementViewModelRegistrationTests() + { + _tempFile = Path.GetTempFileName(); + File.Delete(_tempFile); + _mockBranding = new Mock(); + _mockAppReg = new Mock(); + _graphClientFactory = new GraphClientFactory(new MsalClientFactory()); + } + + public void Dispose() + { + if (File.Exists(_tempFile)) File.Delete(_tempFile); + if (File.Exists(_tempFile + ".tmp")) File.Delete(_tempFile + ".tmp"); + } + + private ProfileManagementViewModel CreateViewModel() + { + var profileService = new ProfileService(new ProfileRepository(_tempFile)); + return new ProfileManagementViewModel( + profileService, + _mockBranding.Object, + _graphClientFactory, + NullLogger.Instance, + _mockAppReg.Object); + } + + private static TenantProfile MakeProfile(string? appId = null) => new TenantProfile + { + Name = "TestTenant", + TenantUrl = "https://test.sharepoint.com", + ClientId = "00000000-0000-0000-0000-000000000001", + AppId = appId + }; + + [Fact] + public void RegisterAppCommand_CanExecute_WhenProfileSelected_AndNoAppId() + { + var vm = CreateViewModel(); + vm.SelectedProfile = MakeProfile(appId: null); + + Assert.True(vm.RegisterAppCommand.CanExecute(null)); + } + + [Fact] + public void RegisterAppCommand_CannotExecute_WhenNoProfile() + { + var vm = CreateViewModel(); + + Assert.False(vm.RegisterAppCommand.CanExecute(null)); + } + + [Fact] + public void RemoveAppCommand_CanExecute_WhenProfileHasAppId() + { + var vm = CreateViewModel(); + vm.SelectedProfile = MakeProfile(appId: "some-app-id"); + + Assert.True(vm.RemoveAppCommand.CanExecute(null)); + } + + [Fact] + public void RemoveAppCommand_CannotExecute_WhenNoAppId() + { + var vm = CreateViewModel(); + vm.SelectedProfile = MakeProfile(appId: null); + + Assert.False(vm.RemoveAppCommand.CanExecute(null)); + } + + [Fact] + public async Task RegisterApp_ShowsFallback_WhenNotAdmin() + { + _mockAppReg + .Setup(s => s.IsGlobalAdminAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(false); + + var vm = CreateViewModel(); + vm.SelectedProfile = MakeProfile(appId: null); + + await vm.RegisterAppCommand.ExecuteAsync(null); + + Assert.True(vm.ShowFallbackInstructions); + } + + [Fact] + public async Task RegisterApp_SetsAppId_OnSuccess() + { + _mockAppReg + .Setup(s => s.IsGlobalAdminAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + _mockAppReg + .Setup(s => s.RegisterAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(AppRegistrationResult.Success("new-app-id-123")); + + var profileService = new ProfileService(new ProfileRepository(_tempFile)); + var profile = MakeProfile(appId: null); + await profileService.AddProfileAsync(profile); + + var vm = new ProfileManagementViewModel( + profileService, + _mockBranding.Object, + _graphClientFactory, + NullLogger.Instance, + _mockAppReg.Object); + vm.SelectedProfile = profile; + + await vm.RegisterAppCommand.ExecuteAsync(null); + + Assert.Equal("new-app-id-123", profile.AppId); + } + + [Fact] + public async Task RemoveApp_ClearsAppId() + { + _mockAppReg + .Setup(s => s.RemoveAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + _mockAppReg + .Setup(s => s.ClearMsalSessionAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + var profileService = new ProfileService(new ProfileRepository(_tempFile)); + var profile = MakeProfile(appId: "existing-app-id"); + await profileService.AddProfileAsync(profile); + + var vm = new ProfileManagementViewModel( + profileService, + _mockBranding.Object, + _graphClientFactory, + NullLogger.Instance, + _mockAppReg.Object); + vm.SelectedProfile = profile; + + await vm.RemoveAppCommand.ExecuteAsync(null); + + Assert.Null(profile.AppId); + } +} diff --git a/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml b/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml index ba1ce9f..aca0801 100644 --- a/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml +++ b/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml @@ -2,9 +2,12 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:loc="clr-namespace:SharepointToolbox.Localization" - Title="Manage Profiles" Width="500" Height="620" + Title="Manage Profiles" Width="500" Height="750" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize"> + + + @@ -12,6 +15,7 @@ + @@ -83,8 +87,47 @@ Visibility="{Binding ValidationMessage, Converter={StaticResource StringToVisibilityConverter}}" /> + + +