Skip to content

Commit

Permalink
fix(vscode): Resolve Test Folder Addition, Naming Convention, and Fil…
Browse files Browse the repository at this point in the history
…e Path Issues in Templates (#6001)

* clean UX for codeful and update c# project for sdk support

* Apply suggestions from code review

Apply pull request review suggestions

Co-authored-by: Esther Fan <[email protected]>

* Apply suggestions from code review

Update mock data trigger docstring

Co-authored-by: Esther Fan <[email protected]>

* Apply suggestions from code review

Co-authored-by: Esther Fan <[email protected]>

* update doc template

* update doc template within assets folder

* update doc templates based on project req

* ensure test folder is in workspace and update templates

* update templates and add root path to .cs file

* update .cs template per PR feedback

* remove var

* update mock path to original

---------

Co-authored-by: Esther Fan <[email protected]>
  • Loading branch information
samikay101 and ecfan authored Nov 6, 2024
1 parent a746b60 commit 1504fc5
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import axios from 'axios';
import { ext } from '../../../../extensionVariables';
import { unzipLogicAppArtifacts } from '../../../utils/taskUtils';
import { isNullOrUndefined } from '@microsoft/logic-apps-shared';
import { FileManagement } from '../../generateDeploymentScripts/iacGestureHelperFunctions';

/**
* Creates a unit test for a Logic App workflow (codeful only).
Expand Down Expand Up @@ -166,6 +167,16 @@ async function generateCodefulUnitTest(
localize('info.generateCodefulUnitTest', 'Generated unit test "{0}" in "{1}"', unitTestName, unitTestFolderPath)
);

// Check if testsDirectory is already part of the workspace
const workspaceFolders = vscode.workspace.workspaceFolders || [];
const isTestsDirectoryInWorkspace = workspaceFolders.some((folder) => folder.uri.fsPath === testsDirectory);

if (!isTestsDirectoryInWorkspace) {
// Add testsDirectory to workspace if not already included
ext.outputChannel.appendLog(localize('addingTestsDirectory', 'Adding tests directory to workspace: {0}', testsDirectory));
FileManagement.addFolderToWorkspace(testsDirectory);
}

context.telemetry.properties.unitTestGenerationStatus = 'Success';
} catch (error) {
context.telemetry.properties.unitTestGenerationStatus = 'Failed';
Expand Down Expand Up @@ -228,14 +239,15 @@ async function createCsFile(unitTestFolderPath: string, unitTestName: string, wo
const csTemplateFileName = 'TestClassFile';
const templatePath = path.join(__dirname, 'assets', templateFolderName, csTemplateFileName);

const templateContent = await fs.readFile(templatePath, 'utf-8');
const csContent = templateContent
let templateContent = await fs.readFile(templatePath, 'utf-8');

templateContent = templateContent
.replace(/<%= UnitTestName %>/g, unitTestName)
.replace(/<%= LogicAppName %>/g, logicAppName)
.replace(/<%= WorkflowName %>/g, workflowName);

const csFilePath = path.join(unitTestFolderPath, `${unitTestName}.cs`);
await fs.writeFile(csFilePath, csContent);
await fs.writeFile(csFilePath, templateContent);

ext.outputChannel.appendLog(localize('csFileCreated', 'Created .cs file at: {0}', csFilePath));
}
Expand Down
104 changes: 55 additions & 49 deletions apps/vs-code-designer/src/assets/UnitTestTemplates/TestClassFile
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
using Microsoft.Azure.Workflows.UnitTesting;
using Microsoft.Azure.Workflows.UnitTesting.Definitions;
using Newtonsoft.Json.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace <%= LogicAppName %>.Tests
{
using Microsoft.Azure.Workflows.UnitTesting;
using Microsoft.Azure.Workflows.UnitTesting.Definitions;
using Newtonsoft.Json.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

/// <summary>
/// The unit test class for testing the workflow named <%= WorkflowName %>.
/// This class is automatically generated when you choose to create a unit test from a workflow run in Visual Studio Code.
/// You can edit this class to modify the test, mock data, and assertions as necessary.
/// </summary>
[TestClass]
public class <%= UnitTestName %>
{
/// <summary>
/// The relative path to the workflow JSON file.
/// The root path of the workspace project directory.
/// </summary>
private readonly string rootPath;

/// <summary>
/// The full path to the workflow JSON file.
/// </summary>
private string workflowDefinitionPath;
private readonly string workflowDefinitionPath;

/// <summary>
/// The relative path to the connections JSON file.
/// The full path to the connections JSON file.
/// </summary>
private string connectionsPath;
private readonly string connectionsPath;

/// <summary>
/// The relative path to the parameters JSON file.
/// The full path to the parameters JSON file.
/// </summary>
private string parametersPath;
private readonly string parametersPath;

/// <summary>
/// The relative path to the local settings JSON file.
/// The full path to the local settings JSON file.
/// </summary>
private string localSettingsPath;
private readonly string localSettingsPath;

/// <summary>
/// The mocked trigger outputs that are generated from a workflow run.
Expand All @@ -47,21 +48,22 @@ namespace <%= LogicAppName %>.Tests
/// </summary>
public Dictionary<string, ActionMock> actionMocks;

/// <summary>
/// <summary>
/// Initializes a new instance of the class named <see cref="<%= UnitTestName %>" />.
/// This constructor loads the necessary paths for the workflow, connections, parameters, and local settings JSON files.
/// The constructor also loads the mock data from the file named <%= UnitTestName %>-mock.json.
/// </summary>
public <%= UnitTestName %>()
{
this.workflowDefinitionPath = @"../<%= logicAppName %>/<%= workflowName %>/workflow.json";
this.connectionsPath = @"../<%= logicAppName %>/connections.json";
this.parametersPath = @"../<%= logicAppName %>/parameters.json";
this.localSettingsPath = @"../<%= logicAppName %>/local.settings.json";
// Set the path for workflow-related input files in the workspace and build the full paths to the required JSON files.
this.rootPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\.."));
this.workflowDefinitionPath = Path.Combine(rootPath, "<%= LogicAppName %>", "<%= WorkflowName %>", "workflow.json");
this.connectionsPath = Path.Combine(rootPath, "<%= LogicAppName %>", "connections.json");
this.parametersPath = Path.Combine(rootPath, "<%= LogicAppName %>", "parameters.json");
this.localSettingsPath = Path.Combine(rootPath, "<%= LogicAppName %>", "local.settings.json");

// Load the mock data from the file named <%= UnitTestName %>-mock.json.
var mockData = JObject.Parse(File.ReadAllText(@"<%= UnitTestName %>-mock.json"));
this.triggerMock = mockData["triggerMocks"].ToObject<Dictionary<string, TriggerMock>>().FirstOrDefault().Value;
// Load the mock data
var mockDataPath = Path.Combine(rootPath, "Tests", "<%= LogicAppName %>", "<%= WorkflowName %>", "<%= UnitTestName %>", "<%= UnitTestName %>-mock.json");
var mockData = JObject.Parse(File.ReadAllText(mockDataPath));
this.triggerMock = mockData["triggerMocks"]?.ToObject<Dictionary<string, TriggerMock>>()?.Values.FirstOrDefault();
this.actionMocks = mockData["actionMocks"].ToObject<Dictionary<string, ActionMock>>();
}

Expand All @@ -73,47 +75,51 @@ namespace <%= LogicAppName %>.Tests
public async Task <%= WorkflowName %>_<%= UnitTestName %>_ExecuteWorkflow_SUCCESS()
{
// PREPARE Mock
// Set up mock data for the action named '<actionName>'.
var actionMocks = this.actionMocks;
actionMocks["<actionName>"] = new ActionMock(
"<actionName>",
TestWorkflowStatus.Succeeded,
onGetActionOutputsCallback: this.Mock<actionName>OutputCallback
);
// var actionMocks = this.actionMocks;
// actionMocks["<actionName>"] = new ActionMock(
// "<actionName>",
// TestWorkflowStatus.Succeeded,
// onGetActionOutputsCallback: this.MockActionOutputCallback
// );

// ACT
// Create an instance of UnitTestExecutor, and run the workflow with the mock data.
var executor = new UnitTestExecutor(this.workflowDefinitionPath, this.connectionsPath, this.parametersPath, this.localSettingsPath);
var testRun = await executor.RunWorkflowAsync(this.triggerMock, actionMocks).ConfigureAwait(false);
// ACT
// Create an instance of UnitTestExecutor, and run the workflow with the mock data.
var executor = new UnitTestExecutor(
workflowFilePath: this.workflowDefinitionPath,
connectionsFilePath: this.connectionsPath,
parametersFilePath: this.parametersPath,
localSettingsFilePath: this.localSettingsPath
);
var testRun = await executor.RunWorkflowAsync(triggerMock: this.triggerMock, actionMocks: actionMocks).ConfigureAwait(continueOnCapturedContext: false);

// ASSERT
// Verify that the workflow executed successfully, and the status is 'Succeeded'.
Assert.IsNotNull(testRun);
Assert.AreEqual(TestWorkflowStatus.Succeeded, testRun.Status);
Assert.IsNotNull(value: testRun);
Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: testRun.Status);
}

#region Mock generator helpers

/// <summary>
/// <summary>
/// The callback method to dynamically generate mocked data for the action named '<actionName>'.
/// You can modify this method to return different mock outputs based on the test scenario.
/// </summary>
/// <param name="context">The test execution context that contains information about the current test run.</param>
public JToken Mock<actionName>OutputCallback(TestExecutionContext context)
public JToken MockActionOutputCallback(TestExecutionContext context)
{
// Sample mock data 1: Replace <your-test-JSON-value> with the appropriate mock data for the test.
// return JObject.Parse(@"
// {
// 'statusCode': '200',
// 'body': {
// <your-test-JSON value>
// <your-test-JSON-value>
// }
// }");

// Sample mock data 2: Modify the existing mocked data dynamically for <actionName>.
// var mockDataToModify = this.actionMocks["<actionName>"];
// mockDataToModify.Outputs["body"]["<property1Name>"] = "<your-test-string-value>";
// return mockDataToModify.Outputs;
return new JObject();
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest" Version="3.2.0" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.10.1" />
<PackageReference Include="Microsoft.Azure.Workflows.WebJobs.Extension" Version="17.10.*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
<PackageReference Include="Microsoft.Azure.Workflows.WebJobs.Extension" Version="1.96.*" />
</ItemGroup>
</Project>
5 changes: 2 additions & 3 deletions apps/vs-code-designer/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export async function activate(context: vscode.ExtensionContext) {
promptParameterizeConnections(activateContext);
verifyLocalConnectionKeys(activateContext);
await startOnboarding(activateContext);
// Comment out Test Explorer and removed import for codeful approach
// await prepareTestExplorer(context, activateContext);
//await prepareTestExplorer(context, activateContext);

ext.extensionVersion = getExtensionVersion();
ext.rgApi = await getResourceGroupsApi();
Expand Down Expand Up @@ -110,4 +109,4 @@ export function deactivate(): Promise<any> {
return undefined;
}

perfStats.loadEndTime = Date.now();
perfStats.loadEndTime = Date.now();

0 comments on commit 1504fc5

Please sign in to comment.