// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Agents.AI.Purview.Models.Common; using Microsoft.Agents.AI.Purview.Models.Jobs; using Microsoft.Agents.AI.Purview.Models.Requests; using Microsoft.Agents.AI.Purview.Models.Responses; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging.Abstractions; using Moq; namespace Microsoft.Agents.AI.Purview.UnitTests; /// /// Unit tests for the class. /// public sealed class ScopedContentProcessorTests { private readonly Mock _mockPurviewClient; private readonly Mock _mockCacheProvider; private readonly Mock _mockChannelHandler; private readonly ScopedContentProcessor _processor; public ScopedContentProcessorTests() { this._mockPurviewClient = new Mock(); this._mockCacheProvider = new Mock(); this._mockChannelHandler = new Mock(); this._processor = new ScopedContentProcessor( this._mockPurviewClient.Object, this._mockCacheProvider.Object, this._mockChannelHandler.Object); } #region ProcessMessagesAsync Tests [Fact] public async Task ProcessMessagesAsync_WithBlockAccessAction_ReturnsShouldBlockTrueAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new ("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); var pcResponse = new ProcessContentResponse { PolicyActions = [ new() { Action = DlpAction.BlockAccess } ] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert Assert.True(result.shouldBlock); Assert.Equal("user-123", result.userId); } [Fact] public async Task ProcessMessagesAsync_WithRestrictionActionBlock_ReturnsShouldBlockTrueAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new ("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); var pcResponse = new ProcessContentResponse { PolicyActions = [ new() { RestrictionAction = RestrictionAction.Block } ] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert Assert.True(result.shouldBlock); Assert.Equal("user-123", result.userId); } [Fact] public async Task ProcessMessagesAsync_WithNoBlockingActions_ReturnsShouldBlockFalseAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); var pcResponse = new ProcessContentResponse { PolicyActions = [ new() { Action = DlpAction.NotifyUser } ] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert Assert.False(result.shouldBlock); Assert.Equal("user-123", result.userId); } [Fact] public async Task ProcessMessagesAsync_DeduplicatesCombinedPolicyActionsByActionAndRestrictionAsync() { // Arrange List messages = [ new(ChatRole.User, "Test message") ]; PurviewSettings settings = CreateValidPurviewSettings(); TokenInfo tokenInfo = new() { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; DlpActionInfo processContentAction = new() { Action = DlpAction.BlockAccess, RestrictionAction = RestrictionAction.Block }; DlpActionInfo duplicateScopeAction = new() { Action = DlpAction.BlockAccess, RestrictionAction = RestrictionAction.Block }; DlpActionInfo restrictionOnlyAction = new() { RestrictionAction = RestrictionAction.Block }; ProcessContentResponse pcResponse = new() { PolicyActions = [ processContentAction ] }; ProtectionScopesResponse psResponse = new() { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline, PolicyActions = [ duplicateScopeAction, restrictionOnlyAction ] } ] }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert Assert.NotNull(pcResponse.PolicyActions); Assert.Equal(2, pcResponse.PolicyActions.Count); Assert.Same(processContentAction, pcResponse.PolicyActions[0]); Assert.Same(restrictionOnlyAction, pcResponse.PolicyActions[1]); } [Fact] public void CheckApplicableScopes_MatchesAnyLocationInScope() { // Arrange ProcessContentRequest pcRequest = CreateProcessContentRequest(); ProtectionScopesResponse psResponse = new() { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new("microsoft.graph.policyLocationApplication", "app-123"), new("microsoft.graph.policyLocationApplication", "different-app") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; // Act (bool shouldProcess, _, ExecutionMode executionMode) = ScopedContentProcessor.CheckApplicableScopes(pcRequest, psResponse); // Assert Assert.True(shouldProcess); Assert.Equal(ExecutionMode.EvaluateInline, executionMode); } [Fact] public async Task ProcessMessagesAsync_UsesCachedProtectionScopes_WhenAvailableAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var cachedPsResponse = new ProtectionScopesResponse { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new ("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(cachedPsResponse); var pcResponse = new ProcessContentResponse { PolicyActions = [] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert this._mockPurviewClient.Verify(x => x.GetProtectionScopesAsync( It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task ProcessMessagesAsync_InvalidatesCache_WhenProtectionScopeModifiedAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { ScopeIdentifier = "etag-1", Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new ("microsoft.graph.policyLocationApplication", "app-123") ], ExecutionMode = ExecutionMode.EvaluateInline } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); var pcResponse = new ProcessContentResponse { ProtectionScopeState = ProtectionScopeState.Modified, PolicyActions = [] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert this._mockCacheProvider.Verify(x => x.RemoveAsync( It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task ProcessMessagesAsync_SendsContentActivities_WhenNoApplicableScopesAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [ new() { Activities = ProtectionScopeActivities.UploadText, Locations = [ new ("microsoft.graph.policyLocationApplication", "app-456") ] } ] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert // Content activities are now queued as background jobs, not called directly this._mockChannelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Once); this._mockPurviewClient.Verify(x => x.ProcessContentAsync( It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task ProcessMessagesAsync_WithNoTenantId_ThrowsPurviewExceptionAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = new PurviewSettings("TestApp"); // No TenantId var tokenInfo = new TokenInfo { UserId = "user-123", ClientId = "client-123" }; // No TenantId this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); // Act & Assert var exception = await Assert.ThrowsAsync(() => this._processor.ProcessMessagesAsync(messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None)); Assert.Contains("No tenant id provided or inferred", exception.Message); } [Fact] public async Task ProcessMessagesAsync_WithNoUserId_ThrowsPurviewExceptionAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", ClientId = "client-123" }; // No UserId this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); // Act & Assert var exception = await Assert.ThrowsAsync(() => this._processor.ProcessMessagesAsync(messages, "session-123", Activity.UploadText, settings, null, CancellationToken.None)); Assert.Contains("No user id provided or inferred", exception.Message); } [Fact] public async Task ProcessMessagesAsync_ExtractsUserIdFromMessageAdditionalProperties_Async() { // Arrange var messages = new List { new (ChatRole.User, "Test message") { AdditionalProperties = new AdditionalPropertiesDictionary { { "userId", "user-from-props" } } } }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, null, CancellationToken.None); // Assert Assert.Equal("user-from-props", result.userId); } [Fact] public async Task ProcessMessagesAsync_ExtractsUserIdFromMessageAuthorName_WhenValidGuidAsync() { // Arrange var userId = Guid.NewGuid().ToString(); var messages = new List { new (ChatRole.User, "Test message") { AuthorName = userId } }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); var psResponse = new ProtectionScopesResponse { Scopes = [] }; this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(psResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, null, CancellationToken.None); // Assert Assert.Equal(userId, result.userId); } [Fact] public async Task ProcessMessagesAsync_CacheMiss_QueuesScopeRetrievalJobAndCallsProcessContentAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync((ProtectionScopesResponse?)null); this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new ProcessContentResponse()); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert: ProcessContent runs in the foreground; GetProtectionScopes is queued as a background job. this._mockPurviewClient.Verify(x => x.ProcessContentAsync( It.IsAny(), It.IsAny()), Times.Once); this._mockPurviewClient.Verify(x => x.GetProtectionScopesAsync( It.IsAny(), It.IsAny()), Times.Never); this._mockChannelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Once); } [Fact] public async Task ProcessMessagesAsync_CacheMiss_WithProcessContentBlockAction_ReturnsShouldBlockTrueAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync((ProtectionScopesResponse?)null); var pcResponse = new ProcessContentResponse { PolicyActions = [ new() { Action = DlpAction.BlockAccess } ] }; this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(pcResponse); // Act var result = await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert Assert.True(result.shouldBlock); this._mockChannelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Once); } [Fact] public async Task ProcessMessagesAsync_CacheMiss_StillCallsProcessContentWhenScopeJobCannotQueueAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync((ProtectionScopesResponse?)null); this._mockChannelHandler.Setup(x => x.QueueJob(It.IsAny())) .Throws(new PurviewJobException("queue unavailable")); this._mockPurviewClient.Setup(x => x.ProcessContentAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new ProcessContentResponse()); // Act await this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None); // Assert: scope warmup is attempted, and ProcessContent still runs when it can't be queued. this._mockChannelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Once); this._mockPurviewClient.Verify(x => x.ProcessContentAsync( It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task ProcessMessagesAsync_WithCachedPaymentRequiredState_ThrowsPaymentRequiredAsync() { // Arrange var messages = new List { new (ChatRole.User, "Test message") }; var settings = CreateValidPurviewSettings(); var tokenInfo = new TokenInfo { TenantId = "tenant-123", UserId = "user-123", ClientId = "client-123" }; this._mockPurviewClient.Setup(x => x.GetUserInfoFromTokenAsync(It.IsAny(), null)) .ReturnsAsync(tokenInfo); this._mockCacheProvider.Setup(x => x.GetAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new PaymentRequiredCacheEntry("Payment required")); // Act + Assert await Assert.ThrowsAsync(() => this._processor.ProcessMessagesAsync( messages, "session-123", Activity.UploadText, settings, "user-123", CancellationToken.None)); this._mockPurviewClient.Verify(x => x.ProcessContentAsync( It.IsAny(), It.IsAny()), Times.Never); this._mockChannelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Never); } [Fact] public async Task BackgroundJobRunner_ScopeRetrievalPaymentRequired_CachesForSubsequentCallsAsync() { // Arrange Func, Task>? runner = null; Mock channelHandler = new(); Mock purviewClient = new(); Mock cacheProvider = new(); PurviewSettings settings = new("TestApp") { MaxConcurrentJobConsumers = 1 }; ProtectionScopesRequest request = new("user-123", "tenant-123") { Activities = ProtectionScopeActivities.UploadText, Locations = [ new("microsoft.graph.policyLocationApplication", "app-123") ] }; ProtectionScopesCacheKey cacheKey = new(request); Channel channel = Channel.CreateUnbounded(); channelHandler.Setup(x => x.AddRunner(It.IsAny, Task>>())) .Callback, Task>>(callback => runner = callback); purviewClient.Setup(x => x.GetProtectionScopesAsync(It.IsAny(), It.IsAny())) .ThrowsAsync(new PurviewPaymentRequiredException("Payment required")); _ = new BackgroundJobRunner(channelHandler.Object, purviewClient.Object, cacheProvider.Object, NullLogger.Instance, settings); // Act Assert.NotNull(runner); await channel.Writer.WriteAsync(new ScopeRetrievalJob(request, cacheKey, CreateProcessContentRequest())); channel.Writer.Complete(); await runner(channel); // Assert cacheProvider.Verify(x => x.SetAsync( It.Is(key => key.TenantId == "tenant-123"), It.Is(entry => entry.Message == "Payment required"), It.IsAny()), Times.Once); } [Fact] public async Task BackgroundJobRunner_ScopeRetrievalNoApplicableScopes_QueuesContentActivityJobAsync() { // Arrange Func, Task>? runner = null; Mock channelHandler = new(); Mock purviewClient = new(); Mock cacheProvider = new(); PurviewSettings settings = new("TestApp") { MaxConcurrentJobConsumers = 1 }; ProtectionScopesRequest request = CreateProtectionScopesRequest(); ScopeRetrievalJob job = new(request, new ProtectionScopesCacheKey(request), CreateProcessContentRequest()); Channel channel = Channel.CreateUnbounded(); channelHandler.Setup(x => x.AddRunner(It.IsAny, Task>>())) .Callback, Task>>(callback => runner = callback); purviewClient.Setup(x => x.GetProtectionScopesAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new ProtectionScopesResponse { Scopes = [] }); _ = new BackgroundJobRunner(channelHandler.Object, purviewClient.Object, cacheProvider.Object, NullLogger.Instance, settings); // Act Assert.NotNull(runner); await channel.Writer.WriteAsync(job); channel.Writer.Complete(); await runner(channel); // Assert channelHandler.Verify(x => x.QueueJob(It.IsAny()), Times.Once); } #endregion #region Helper Methods private static ProtectionScopesRequest CreateProtectionScopesRequest() { return new ProtectionScopesRequest("user-123", "tenant-123") { Activities = ProtectionScopeActivities.UploadText, Locations = [ new("microsoft.graph.policyLocationApplication", "app-123") ] }; } private static ProcessContentRequest CreateProcessContentRequest() { PurviewTextContent content = new("Test content"); ProcessConversationMetadata metadata = new(content, "msg-123", false, "Test message", "test-correlation-id"); ActivityMetadata activityMetadata = new(Activity.UploadText); DeviceMetadata deviceMetadata = new() { OperatingSystemSpecifications = new() { OperatingSystemPlatform = "Windows", OperatingSystemVersion = "10" } }; IntegratedAppMetadata integratedAppMetadata = new() { Name = "TestApp", Version = "1.0" }; PolicyLocation policyLocation = new("microsoft.graph.policyLocationApplication", "app-123"); ProtectedAppMetadata protectedAppMetadata = new(policyLocation) { Name = "TestApp", Version = "1.0" }; ContentToProcess contentToProcess = new( [metadata], activityMetadata, deviceMetadata, integratedAppMetadata, protectedAppMetadata); return new ProcessContentRequest(contentToProcess, "user-123", "tenant-123"); } private static PurviewSettings CreateValidPurviewSettings() { return new PurviewSettings("TestApp") { TenantId = "tenant-123", PurviewAppLocation = new PurviewAppLocation(PurviewLocationType.Application, "app-123") }; } #endregion }