Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CopilotStudio.McsCore/CopilotStudio.McsCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<InternalsVisibleTo Include="Microsoft.PowerPlatformLS.Impl.PullAgent, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.PowerPlatformLS.Impl.Core, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.PowerPlatformLS.Impl.Language.CopilotStudio, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.PowerPlatformLS.Impl.Language.Yaml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.CopilotStudio.Sync.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.CopilotStudio.Sync.TestHarness, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Microsoft.PowerPlatformLS.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
Expand Down
131 changes: 131 additions & 0 deletions src/CopilotStudio.Sync.UnitTests/AiPromptPushTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (C) Microsoft Corporation. All rights reserved.

using Microsoft.CopilotStudio.McsCore;
using Microsoft.CopilotStudio.Sync.Dataverse;
using Moq;
using Xunit;

namespace Microsoft.CopilotStudio.Sync.UnitTests;

public class AiPromptPushTests : IDisposable
{
private readonly string _root;
private readonly DirectoryPath _workspace;
private readonly Guid _modelId = Guid.NewGuid();
private readonly string _promptFolder;

public AiPromptPushTests()
{
_root = Path.Combine(Path.GetTempPath(), "mcs-aiprompt-perf-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_root);
_workspace = new DirectoryPath(_root.Replace('\\', '/') + "/");
_promptFolder = Path.Combine(_root, "prompts", "MyPrompt-" + _modelId.ToString("D"));
Directory.CreateDirectory(_promptFolder);
}

public void Dispose()
{
try
{
Directory.Delete(_root, recursive: true);
}
catch (IOException)
{
}
}

private static WorkspaceSynchronizer CreateSynchronizer()
{
var fileParser = new SyncMcsFileParser(LspProjectorService.Instance);
var fileAccessorFactory = new FileAccessorFactory();
var island = new Mock<IIslandControlPlaneService>();
var progress = new TestSyncProgress(new List<string>());
var pathResolver = new LspComponentPathResolver();

return new WorkspaceSynchronizer(fileParser, fileAccessorFactory, island.Object, progress, pathResolver);
}

private void WritePromptFiles(string instruction)
{
File.WriteAllText(Path.Combine(_promptFolder, "metadata.yml"), "name: My Prompt\n");
File.WriteAllText(Path.Combine(_promptFolder, "prompt.json"), "{ \"instruction\": \"" + instruction + "\" }");
}

[Fact]
public async Task UpsertAIPromptsForAgentAsync_UnchangedSinceLastPush_SkipsUpsert()
{
var synchronizer = CreateSynchronizer();
WritePromptFiles("Summarize the input.");

var dataverse = new Mock<ISyncDataverseClient>();
dataverse
.Setup(c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new SyncDataverseClient.AIPromptResponse { PromptName = "My Prompt", ErrorMessage = string.Empty });

await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);
await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);

dataverse.Verify(
c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()),
Times.Once);
}

[Fact]
public async Task UpsertAIPromptsForAgentAsync_UnchangedSinceLastPush_StillReturnsMetadataForCache()
{
var synchronizer = CreateSynchronizer();
WritePromptFiles("Summarize the input.");

var dataverse = new Mock<ISyncDataverseClient>();
dataverse
.Setup(c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new SyncDataverseClient.AIPromptResponse { PromptName = "My Prompt", ErrorMessage = string.Empty });

await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);
var (responses, prompts) = await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);

Assert.Empty(responses);
var metadata = Assert.Single(prompts);
Assert.Equal(_modelId, metadata.AIModelId);
}

[Fact]
public async Task UpsertAIPromptsForAgentAsync_PromptContentChanged_ReUpserts()
{
var synchronizer = CreateSynchronizer();
WritePromptFiles("Summarize the input.");

var dataverse = new Mock<ISyncDataverseClient>();
dataverse
.Setup(c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new SyncDataverseClient.AIPromptResponse { PromptName = "My Prompt", ErrorMessage = string.Empty });

await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);

WritePromptFiles("Translate the input to French.");
await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);

dataverse.Verify(
c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()),
Times.Exactly(2));
}

[Fact]
public async Task UpsertAIPromptsForAgentAsync_PublishFails_DoesNotRecordBaseline()
{
var synchronizer = CreateSynchronizer();
WritePromptFiles("Summarize the input.");

var dataverse = new Mock<ISyncDataverseClient>();
dataverse
.Setup(c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new SyncDataverseClient.AIPromptResponse { PromptName = "My Prompt", ErrorMessage = "boom" });

await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);
await synchronizer.UpsertAIPromptsForAgentAsync(_workspace, dataverse.Object, Guid.NewGuid(), CancellationToken.None);

dataverse.Verify(
c => c.UpsertAIPromptAsync(It.IsAny<Guid?>(), It.IsAny<SyncDataverseClient.AIPromptMetadata>(), It.IsAny<CancellationToken>()),
Times.Exactly(2));
}
}
Loading
Loading