in apps/vs-code-designer/src/app/commands/workflows/unitTest/createUnitTest.ts [118:327]
async function generateUnitTestFromRun(
context: IActionContext,
projectPath: string,
workflowName: string,
unitTestName: string,
runId: string,
unitTestDefinition: any,
workflowPath: string
): Promise<void> {
// Initialize telemetry properties
Object.assign(context.telemetry.properties, {
apiCallInitiated: 'false',
apiCallSucceeded: 'false',
filesUnzipped: 'false',
csFileCreated: 'false',
csprojFileCreated: 'false',
nugetConfigFileCreated: 'false',
testsFolderAddedToWorkspace: 'false',
});
// Get parsed outputs
const parsedOutputs = await parseUnitTestOutputs(unitTestDefinition);
const operationInfo = parsedOutputs['operationInfo'];
const outputParameters = parsedOutputs['outputParameters'];
logTelemetry(context, {
operationInfoExists: operationInfo ? 'true' : 'false',
outputParametersExists: outputParameters ? 'true' : 'false',
});
const startTime = Date.now();
try {
if (!runId) {
logTelemetry(context, { runIdMissing: 'true' });
throw new Error(localize('runIdMissing', 'Run ID is required to generate a codeful unit test.'));
}
if (!ext.workflowRuntimePort) {
logTelemetry(context, { missingRuntimePort: 'true' });
throw new Error(localize('workflowRuntimeNotRunning', 'Workflow runtime is not running. Start the runtime and try again.'));
}
logTelemetry(context, { runtimePort: ext.workflowRuntimePort.toString() });
const baseUrl = `http://localhost:${ext.workflowRuntimePort}`;
const apiUrl = `${baseUrl}/runtime/webhooks/workflow/api/management/workflows/${encodeURIComponent(workflowName)}/runs/${encodeURIComponent(runId)}/generateUnitTest`;
ext.outputChannel.appendLog(localize('apiUrl', `Calling API URL: ${apiUrl}`));
ext.outputChannel.appendLog(
localize(
'operationalContext',
`Operational context: Workflow Name: ${workflowName}, Run ID: ${runId}, Unit Test Name: ${unitTestName}`
)
);
ext.outputChannel.appendLog(localize('initiatingApiCall', 'Initiating Unit Test Generation API call...'));
logTelemetry(context, { apiCallInitiated: 'true' });
let response: any;
try {
response = await axios.post(
apiUrl,
{ UnitTestName: unitTestName },
{
headers: {
Accept: 'application/zip',
'Content-Type': 'application/json',
},
responseType: 'arraybuffer',
}
);
logTelemetry(context, { apiCallSucceeded: 'true', processStage: 'API Call Completed' });
ext.outputChannel.appendLog(localize('apiCallSuccessful', 'API call successful, processing response...'));
} catch (apiError) {
const failReason = parseErrorBeforeTelemetry(apiError);
logTelemetry(context, {
apiCallSucceeded: 'false',
apiCallFailReason: failReason,
});
ext.outputChannel.appendLog(localize('apiCallFailedLog', `API call failed: ${context.telemetry.properties.apiCallFailReason}`));
throw apiError;
}
const zipBuffer = Buffer.from(response.data);
const contentType = response.headers['content-type'];
if (contentType !== 'application/zip') {
logTelemetry(context, { apiCallSucceeded: 'false' });
throw new Error(localize('invalidResponseType', `Expected a zip file but received ${contentType}`));
}
const paths = getUnitTestPaths(projectPath, workflowName, unitTestName);
const { mockClassContent, foundActionMocks, foundTriggerMocks } = await getOperationMockClassContent(
operationInfo,
outputParameters,
workflowPath,
workflowName,
paths.logicAppName
);
if (!foundTriggerMocks || Object.keys(foundTriggerMocks).length === 0) {
throw new Error(localize('noTriggersFound', 'No trigger found in the workflow. Unit tests must include a mocked trigger.'));
}
// Get cleaned versions of strings
const cleanedUnitTestName = unitTestName.replace(/-/g, '_');
const cleanedWorkflowName = workflowName.replace(/-/g, '_');
const cleanedLogicAppName = paths.logicAppName.replace(/-/g, '_');
try {
await fse.ensureDir(paths.unitTestFolderPath);
ext.outputChannel.appendLog(localize('unzippingFiles', `Unzipping Mock.json into: ${paths.unitTestFolderPath}`));
await unzipLogicAppArtifacts(zipBuffer, paths.unitTestFolderPath);
logTelemetry(context, { filesUnzipped: 'true', processStage: 'Files Unzipped' });
ext.outputChannel.appendLog(localize('filesUnzipped', 'Files successfully unzipped.'));
context.telemetry.properties.processStage = 'Files Unzipped';
} catch (unzipError) {
const unzipFailReason = parseError(unzipError).message;
logTelemetry(context, { filesUnzipped: 'false', filesUnzipFailReason: unzipFailReason });
throw unzipError;
}
try {
// Create the testSettings.config and TestExecutor.cs files
ext.outputChannel.appendLog(localize('creatingTestSettingsConfig', 'Creating testSettings.config file for unit test...'));
await createTestSettingsConfigFile(paths.workflowTestFolderPath, workflowName, paths.logicAppName);
await createTestExecutorFile(paths.logicAppTestFolderPath, cleanedLogicAppName);
const [actionName, actionOutputClassName] = Object.entries(foundActionMocks)[0] || [];
const [, triggerOutputClassName] = Object.entries(foundTriggerMocks)[0] || [];
// Create actionMockClassName by replacing "Output" with "Mock" in actionOutputClassName
const actionMockClassName = actionOutputClassName?.replace(/(.*)Output$/, '$1Mock');
const triggerMockClassName = triggerOutputClassName.replace(/(.*)Output$/, '$1Mock');
await fse.ensureDir(paths.mocksFolderPath);
for (const [mockClassName, classContent] of Object.entries(mockClassContent)) {
const mockFilePath = path.join(paths.mocksFolderPath, `${mockClassName}.cs`);
await fse.writeFile(mockFilePath, classContent, 'utf-8');
ext.outputChannel.appendLog(localize('csMockFileCreated', 'Created .cs file for mock at: {0}', mockFilePath));
}
await createTestCsFile(
paths.unitTestFolderPath!,
unitTestName,
cleanedUnitTestName,
workflowName,
cleanedWorkflowName,
paths.logicAppName,
cleanedLogicAppName,
actionName,
actionOutputClassName,
actionMockClassName,
triggerOutputClassName,
triggerMockClassName
);
logTelemetry(context, { csFileCreated: 'true' });
} catch (csError) {
const csFileFailReason = parseError(csError).message;
logTelemetry(context, { csFileCreated: 'false', csFileFailReason });
throw csError;
}
try {
await ensureCsproj(paths.testsDirectory, paths.logicAppTestFolderPath, paths.logicAppName);
logTelemetry(context, { nugetConfigFileCreated: 'true' });
} catch (nugetError) {
const nugetConfigFailReason = parseError(nugetError).message;
logTelemetry(context, { nugetConfigFileCreated: 'false', nugetConfigFailReason });
throw nugetError;
}
const csprojFilePath = path.join(paths.logicAppTestFolderPath, `${paths.logicAppName}.csproj`);
const isCsprojUpdated = await updateCsprojFile(csprojFilePath, workflowName);
logTelemetry(context, { csprojUpdated: isCsprojUpdated ? 'true' : 'false' });
try {
ext.outputChannel.appendLog(localize('checkingWorkspace', 'Checking if tests directory is already part of the workspace...'));
await ensureDirectoryInWorkspace(paths.testsDirectory);
context.telemetry.properties.testsFolderAddedToWorkspace = 'true';
ext.outputChannel.appendLog(localize('workspaceUpdated', 'Tests directory added to workspace if not already included.'));
} catch (workspaceError) {
const testsFolderFailReason = parseError(workspaceError).message;
logTelemetry(context, { testsFolderAddedToWorkspace: 'false', testsFolderFailReason });
ext.outputChannel.appendLog(
localize('error.addingTestsDirectory', `Error adding tests directory to workspace: ${parseError(workspaceError).message}`)
);
throw workspaceError;
}
vscode.window.showInformationMessage(
localize('info.generateCodefulUnitTest', `Generated unit test "${unitTestName}" in "${paths.unitTestFolderPath}"`)
);
logTelemetry(context, { unitTestGenerationStatus: 'Success' });
context.telemetry.measurements.generateCodefulUnitTestMs = Date.now() - startTime;
try {
const csprojFilePath = path.join(paths.logicAppTestFolderPath, `${paths.logicAppName}.csproj`);
ext.outputChannel.appendLog(`Updating solution in tests folder: ${paths.testsDirectory}`);
await updateTestsSln(paths.testsDirectory, csprojFilePath);
} catch (solutionError) {
ext.outputChannel.appendLog(`Failed to update solution: ${solutionError}`);
}
} catch (methodError) {
context.telemetry.properties.unitTestGenerationStatus = 'Failed';
const errorMessage = parseErrorBeforeTelemetry(methodError);
logTelemetry(context, { errorMessage });
vscode.window.showErrorMessage(localize('error.generateCodefulUnitTest', `Failed to generate codeful unit test: ${errorMessage}`));
ext.outputChannel.appendLog(localize('error.generateCodefulUnitTest', `Failed to generate codeful unit test: ${errorMessage}`));
throw methodError;
}
}