test(07-10): add debounced search unit test for UserAccessAuditViewModel
- Extended CreateViewModel helper to return (vm, auditMock, graphMock) 3-tuple - Updated all 8 existing tests to use _ discard for the new graphMock slot - Added Test 9: SearchQuery_debounced_calls_SearchUsersAsync verifying that setting SearchQuery to "Ali" calls SearchUsersAsync after 300ms debounce - All 9 ViewModel tests pass; full suite 177 passed / 22 skipped
This commit is contained in:
@@ -41,8 +41,8 @@ public class UserAccessAuditViewModelTests
|
|||||||
string upn = "alice@contoso.com") =>
|
string upn = "alice@contoso.com") =>
|
||||||
new(display, upn, upn);
|
new(display, upn, upn);
|
||||||
|
|
||||||
/// <summary>Creates a ViewModel wired with a mock IUserAccessAuditService.</summary>
|
/// <summary>Creates a ViewModel wired with mock services.</summary>
|
||||||
private static (UserAccessAuditViewModel vm, Mock<IUserAccessAuditService> auditMock)
|
private static (UserAccessAuditViewModel vm, Mock<IUserAccessAuditService> auditMock, Mock<IGraphUserSearchService> graphMock)
|
||||||
CreateViewModel(IReadOnlyList<UserAccessEntry>? auditResult = null)
|
CreateViewModel(IReadOnlyList<UserAccessEntry>? auditResult = null)
|
||||||
{
|
{
|
||||||
var mockAudit = new Mock<IUserAccessAuditService>();
|
var mockAudit = new Mock<IUserAccessAuditService>();
|
||||||
@@ -65,7 +65,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
mockSession.Object,
|
mockSession.Object,
|
||||||
NullLogger<FeatureViewModelBase>.Instance);
|
NullLogger<FeatureViewModelBase>.Instance);
|
||||||
|
|
||||||
return (vm, mockAudit);
|
return (vm, mockAudit, mockGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Test 1: RunOperation calls AuditUsersAsync ────────────────────────────
|
// ── Test 1: RunOperation calls AuditUsersAsync ────────────────────────────
|
||||||
@@ -73,7 +73,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task RunOperation_calls_AuditUsersAsync()
|
public async Task RunOperation_calls_AuditUsersAsync()
|
||||||
{
|
{
|
||||||
var (vm, auditMock) = CreateViewModel();
|
var (vm, auditMock, _) = CreateViewModel();
|
||||||
|
|
||||||
vm.SelectedUsers.Add(MakeUser());
|
vm.SelectedUsers.Add(MakeUser());
|
||||||
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
||||||
@@ -102,7 +102,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
MakeEntry(userLogin: "bob@contoso.com")
|
MakeEntry(userLogin: "bob@contoso.com")
|
||||||
};
|
};
|
||||||
|
|
||||||
var (vm, _) = CreateViewModel(entries);
|
var (vm, _, _) = CreateViewModel(entries);
|
||||||
|
|
||||||
vm.SelectedUsers.Add(MakeUser());
|
vm.SelectedUsers.Add(MakeUser());
|
||||||
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
||||||
@@ -124,7 +124,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
MakeEntry(userLogin: "bob@contoso.com", siteUrl: "https://contoso.sharepoint.com/sites/s1", isHighPrivilege: true)
|
MakeEntry(userLogin: "bob@contoso.com", siteUrl: "https://contoso.sharepoint.com/sites/s1", isHighPrivilege: true)
|
||||||
};
|
};
|
||||||
|
|
||||||
var (vm, _) = CreateViewModel(entries);
|
var (vm, _, _) = CreateViewModel(entries);
|
||||||
|
|
||||||
vm.SelectedUsers.Add(MakeUser());
|
vm.SelectedUsers.Add(MakeUser());
|
||||||
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
||||||
@@ -142,7 +142,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
public async Task OnTenantSwitched_resets_state()
|
public async Task OnTenantSwitched_resets_state()
|
||||||
{
|
{
|
||||||
var entries = new List<UserAccessEntry> { MakeEntry() };
|
var entries = new List<UserAccessEntry> { MakeEntry() };
|
||||||
var (vm, _) = CreateViewModel(entries);
|
var (vm, _, _) = CreateViewModel(entries);
|
||||||
|
|
||||||
// Populate state
|
// Populate state
|
||||||
vm.SelectedUsers.Add(MakeUser());
|
vm.SelectedUsers.Add(MakeUser());
|
||||||
@@ -172,7 +172,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void OnGlobalSitesChanged_updates_selected_sites()
|
public void OnGlobalSitesChanged_updates_selected_sites()
|
||||||
{
|
{
|
||||||
var (vm, _) = CreateViewModel();
|
var (vm, _, _) = CreateViewModel();
|
||||||
var sites = new List<SiteInfo>
|
var sites = new List<SiteInfo>
|
||||||
{
|
{
|
||||||
new("https://contoso.sharepoint.com/sites/hr", "HR"),
|
new("https://contoso.sharepoint.com/sites/hr", "HR"),
|
||||||
@@ -190,7 +190,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void OnGlobalSitesChanged_skipped_when_override()
|
public void OnGlobalSitesChanged_skipped_when_override()
|
||||||
{
|
{
|
||||||
var (vm, _) = CreateViewModel();
|
var (vm, _, _) = CreateViewModel();
|
||||||
|
|
||||||
// Add a local site and set the override flag via reflection
|
// Add a local site and set the override flag via reflection
|
||||||
var localSite = new SiteInfo("https://contoso.sharepoint.com/sites/local", "Local");
|
var localSite = new SiteInfo("https://contoso.sharepoint.com/sites/local", "Local");
|
||||||
@@ -218,7 +218,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void CanExport_false_when_no_results()
|
public void CanExport_false_when_no_results()
|
||||||
{
|
{
|
||||||
var (vm, _) = CreateViewModel();
|
var (vm, _, _) = CreateViewModel();
|
||||||
|
|
||||||
// Results is empty by default
|
// Results is empty by default
|
||||||
Assert.Empty(vm.Results);
|
Assert.Empty(vm.Results);
|
||||||
@@ -232,7 +232,7 @@ public class UserAccessAuditViewModelTests
|
|||||||
public async Task CanExport_true_when_has_results()
|
public async Task CanExport_true_when_has_results()
|
||||||
{
|
{
|
||||||
var entries = new List<UserAccessEntry> { MakeEntry() };
|
var entries = new List<UserAccessEntry> { MakeEntry() };
|
||||||
var (vm, _) = CreateViewModel(entries);
|
var (vm, _, _) = CreateViewModel(entries);
|
||||||
|
|
||||||
vm.SelectedUsers.Add(MakeUser());
|
vm.SelectedUsers.Add(MakeUser());
|
||||||
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
vm.SelectedSites.Add(new SiteInfo("https://contoso.sharepoint.com", "Contoso"));
|
||||||
@@ -243,4 +243,49 @@ public class UserAccessAuditViewModelTests
|
|||||||
Assert.True(vm.ExportCsvCommand.CanExecute(null));
|
Assert.True(vm.ExportCsvCommand.CanExecute(null));
|
||||||
Assert.True(vm.ExportHtmlCommand.CanExecute(null));
|
Assert.True(vm.ExportHtmlCommand.CanExecute(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Test 9: Debounced search triggers SearchUsersAsync ───────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SearchQuery_debounced_calls_SearchUsersAsync()
|
||||||
|
{
|
||||||
|
var graphResults = new List<GraphUserResult>
|
||||||
|
{
|
||||||
|
new("Alice Smith", "alice@contoso.com", "alice@contoso.com")
|
||||||
|
};
|
||||||
|
|
||||||
|
var (vm, _, graphMock) = CreateViewModel();
|
||||||
|
|
||||||
|
graphMock
|
||||||
|
.Setup(s => s.SearchUsersAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.Is<string>(q => q == "Ali"),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(graphResults);
|
||||||
|
|
||||||
|
// Set a TenantProfile so _currentProfile is non-null
|
||||||
|
var profile = new TenantProfile
|
||||||
|
{
|
||||||
|
Name = "Test",
|
||||||
|
TenantUrl = "https://contoso.sharepoint.com",
|
||||||
|
ClientId = "test-client-id"
|
||||||
|
};
|
||||||
|
WeakReferenceMessenger.Default.Send(new TenantSwitchedMessage(profile));
|
||||||
|
|
||||||
|
// Act: set SearchQuery which triggers OnSearchQueryChanged → DebounceSearchAsync
|
||||||
|
vm.SearchQuery = "Ali";
|
||||||
|
|
||||||
|
// Wait longer than 300ms debounce to allow async fire-and-forget to complete
|
||||||
|
await Task.Delay(600);
|
||||||
|
|
||||||
|
// Assert: SearchUsersAsync was called with the query
|
||||||
|
graphMock.Verify(
|
||||||
|
s => s.SearchUsersAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
"Ali",
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()),
|
||||||
|
Times.Once);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user