diff --git a/.planning/phases/06-global-site-selection/06-UAT.md b/.planning/phases/06-global-site-selection/06-UAT.md new file mode 100644 index 0000000..690be5d --- /dev/null +++ b/.planning/phases/06-global-site-selection/06-UAT.md @@ -0,0 +1,118 @@ +--- +status: complete +phase: 06-global-site-selection +source: [06-01-SUMMARY.md, 06-02-SUMMARY.md, 06-03-SUMMARY.md, 06-04-SUMMARY.md, 06-05-SUMMARY.md] +started: 2026-04-07T11:00:00Z +updated: 2026-04-07T11:30:00Z +--- + +## Current Test + +[testing complete] + +## Tests + +### 1. Select Sites Button in Toolbar +expected: After connecting to a tenant, the toolbar shows a "Select Sites" button (localized). Clicking it opens the SitePickerDialog. The button is disabled when no profile is connected. +result: issue +reported: "Cant add a profile to connect to a tenant, the add button stays greyed out. After fix applied, Connect button didn't open browser. After second fix, SitePickerDialog shows parsing error: Must specify valid information for parsing in the string" +severity: blocker + +### 2. Global Sites Count Label +expected: After selecting sites via the global picker, a label next to the button shows the count of selected sites (e.g., "3 sites selected"). When no sites are selected, the label shows the empty state. Label is localized (EN/FR). +result: skipped +reason: Blocked by Test 1 — cannot select sites without working SitePickerDialog + +### 3. Single-Site Tab Pre-Fill (Storage, Search, Duplicates, FolderStructure) +expected: Select one site globally. Switch to Storage/Search/Duplicates/FolderStructure tab — the SiteUrl field is automatically pre-filled with the globally selected site URL. +result: skipped +reason: Blocked by Test 1 — cannot select sites + +### 4. Permissions Tab Multi-Site Pre-Fill +expected: Select multiple sites globally. Switch to the Permissions tab — SelectedSites is pre-populated with all globally selected sites. +result: skipped +reason: Blocked by Test 1 — cannot select sites + +### 5. Transfer Tab Pre-Fill +expected: Select a site globally. Switch to Transfer tab — the SourceSiteUrl field is pre-filled with the globally selected site URL. +result: skipped +reason: Blocked by Test 1 — cannot select sites + +### 6. Local Override Protection +expected: On a single-site tab, manually type a different site URL. Then change the global site selection. The manually-entered URL is NOT overwritten — local input takes priority. +result: skipped +reason: Blocked by Test 1 — cannot select sites + +### 7. Clear Field Reverts to Global +expected: On a single-site tab with a local override active, clear the SiteUrl field (make it empty). The field immediately re-fills with the current global site URL — clearing means "go back to global." +result: skipped +reason: Blocked by Test 1 — cannot select sites + +### 8. Tenant Switch Clears Global Sites +expected: Select sites globally, then switch to a different tenant. The global site selection is cleared (no sites selected). The toolbar label returns to the empty state. Tab SiteUrl fields are cleared. +result: skipped +reason: Blocked by Test 1 — cannot select sites + +## Summary + +total: 8 +passed: 0 +issues: 3 +pending: 0 +skipped: 7 + +## Gaps + +- truth: "Add profile button enables as user fills fields" + status: failed + reason: "User reported: add button stays greyed out — ProfileManagementViewModel missing NotifyCanExecuteChanged on property changes" + severity: blocker + test: 1 + root_cause: "ProfileManagementViewModel.CanAdd() never re-evaluated because ObservableProperty changes for NewName, NewTenantUrl, NewClientId did not call AddCommand.NotifyCanExecuteChanged()" + artifacts: + - path: "SharepointToolbox/ViewModels/ProfileManagementViewModel.cs" + issue: "Missing partial void OnNewNameChanged/OnNewTenantUrlChanged/OnNewClientIdChanged hooks to notify commands" + missing: + - "partial void OnXxxChanged methods that call NotifyCanExecuteChanged on AddCommand and RenameCommand" + fix_applied: true + fix_commit: pending + +- truth: "Clicking Connect opens browser for interactive Microsoft login" + status: failed + reason: "User reported: clicking Connect does nothing, no browser opens, no log output — openBrowserCallback in SessionManager was a no-op" + severity: blocker + test: 1 + root_cause: "SessionManager.GetOrCreateContextAsync openBrowserCallback was empty (comment said MSAL handles it, but PnP CreateWithInteractiveLogin requires the callback to open the browser)" + artifacts: + - path: "SharepointToolbox/Services/SessionManager.cs" + issue: "openBrowserCallback was no-op — needed Process.Start with UseShellExecute" + missing: + - "Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }) in openBrowserCallback" + fix_applied: true + fix_commit: pending + +- truth: "SitePickerDialog loads tenant sites after successful connection" + status: failed + reason: "User reported: error 'Must specify valid information for parsing in the string' in site selector after connecting" + severity: blocker + test: 1 + root_cause: "Likely UriFormatException from PnP Framework internal URL parsing when SiteListService.GetSitesAsync calls GetOrCreateContextAsync with the admin URL, or during Tenant.GetSitePropertiesFromSharePoint" + artifacts: + - path: "SharepointToolbox/Services/SiteListService.cs" + issue: "DeriveAdminUrl or downstream PnP call produces UriFormatException" + missing: + - "Debug with actual tenant URL to identify exact parsing failure point" + fix_applied: false + +- truth: "Connect button disables or changes state after successful connection" + status: failed + reason: "User reported: Connect button stays active after successful connection" + severity: minor + test: 1 + root_cause: "ConnectCommand CanExecute is () => SelectedProfile != null — does not check IsAuthenticated state" + artifacts: + - path: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" + issue: "ConnectCommand CanExecute does not account for connection state" + missing: + - "Add _sessionManager.IsAuthenticated(SelectedProfile.TenantUrl) check to ConnectCommand CanExecute, or change button text to 'Reconnect' after connection" + fix_applied: false diff --git a/SharepointToolbox/Services/SessionManager.cs b/SharepointToolbox/Services/SessionManager.cs index 1e89eb2..c215407 100644 --- a/SharepointToolbox/Services/SessionManager.cs +++ b/SharepointToolbox/Services/SessionManager.cs @@ -60,8 +60,11 @@ public class SessionManager : ISessionManager clientId: profile.ClientId, openBrowserCallback: (url, port) => { - // The browser/WAM flow is handled by MSAL; this callback receives the - // local redirect URL and port. No action needed here — MSAL opens the browser. + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); }, tokenCacheCallback: tokenCache => { diff --git a/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs b/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs index b4e7584..79bd3c0 100644 --- a/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs +++ b/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs @@ -58,6 +58,16 @@ public partial class ProfileManagementViewModel : ObservableObject } } + partial void OnNewNameChanged(string value) => NotifyCommandsCanExecuteChanged(); + partial void OnNewTenantUrlChanged(string value) => NotifyCommandsCanExecuteChanged(); + partial void OnNewClientIdChanged(string value) => NotifyCommandsCanExecuteChanged(); + + private void NotifyCommandsCanExecuteChanged() + { + AddCommand.NotifyCanExecuteChanged(); + RenameCommand.NotifyCanExecuteChanged(); + } + private bool CanAdd() { if (string.IsNullOrWhiteSpace(NewName)) return false;