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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as vscode from 'vscode';
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { ext } from '../../../extensionVariables';
import { hasCodefulWorkflowSetting } from '../../utils/codeful';
import { getLogicAppWithoutCustomCode, getWorkspaceRoot } from '../../utils/workspace';
import { getWorkspaceRoot } from '../../utils/workspace';
import { getEligibleLogicAppFoldersForCustomCode } from '../../utils/customCodeUtils';
import { tryGetLogicAppProjectRoot } from '../../utils/verifyIsProject';
import { cloudToLocal } from '../cloudToLocal/cloudToLocal';
import { ensureWorkspace } from '../ensureWorkspace';
Expand Down Expand Up @@ -54,10 +55,13 @@ vi.mock('../createWorkflow/createLogicAppWorkflow', () => ({
}));

vi.mock('../../utils/workspace', () => ({
getLogicAppWithoutCustomCode: vi.fn(),
getWorkspaceRoot: vi.fn(),
}));

vi.mock('../../utils/customCodeUtils', () => ({
getEligibleLogicAppFoldersForCustomCode: vi.fn(),
}));

vi.mock('../../utils/codeful', () => ({
hasCodefulWorkflowSetting: vi.fn(),
}));
Expand All @@ -73,7 +77,7 @@ function getLastWebviewConfig(): WorkspaceWebviewCommandConfig {

describe('workspace webview command wrappers', () => {
const context = { telemetry: { properties: {}, measurements: {} } } as any;
const workspaceRoot = 'D:\\workspace';
const workspaceRoot = path.resolve(path.sep, 'workspace');
const logicAppRoot = path.join(workspaceRoot, 'LogicApp');

beforeEach(() => {
Expand All @@ -83,7 +87,7 @@ describe('workspace webview command wrappers', () => {
(getWorkspaceRoot as Mock).mockResolvedValue(workspaceRoot);
(tryGetLogicAppProjectRoot as Mock).mockResolvedValue(logicAppRoot);
(hasCodefulWorkflowSetting as Mock).mockResolvedValue(false);
(getLogicAppWithoutCustomCode as Mock).mockResolvedValue([]);
(getEligibleLogicAppFoldersForCustomCode as Mock).mockResolvedValue([]);
});

it('createWorkspace passes workspace config and invokes createLogicAppWorkspace', async () => {
Expand Down Expand Up @@ -126,17 +130,17 @@ describe('workspace webview command wrappers', () => {
});

it('createProject opens the project webview when a workspace is present', async () => {
const workspaceFile = { fsPath: 'D:\\workspace\\MyWorkspace.code-workspace' };
const workspaceFile = { fsPath: path.join(workspaceRoot, 'MyWorkspace.code-workspace') };
const workspaceFileJson = { folders: [{ path: './LogicApp' }] };
const logicAppsWithoutCustomCode = ['LogicApp'];
const eligiblePaths = [path.join(workspaceRoot, 'LogicApp')];
(vscode.workspace as any).workspaceFile = workspaceFile;
(vscode.workspace.fs.readFile as Mock).mockResolvedValue(Buffer.from(JSON.stringify(workspaceFileJson)));
(vscode.workspace.fs.readDirectory as Mock).mockResolvedValue([
['LogicApp', 'directory'],
['CSharpProject', 'directory'],
['MyWorkspace.code-workspace', 'file'],
]);
(getLogicAppWithoutCustomCode as Mock).mockResolvedValue(logicAppsWithoutCustomCode);
(getEligibleLogicAppFoldersForCustomCode as Mock).mockResolvedValue(eligiblePaths);

await createProject(context);

Expand All @@ -147,10 +151,12 @@ describe('workspace webview command wrappers', () => {
projectName: ProjectName.createLogicApp,
createCommand: ExtensionCommand.createLogicApp,
});
const expectedLogicAppPath = path.join(workspaceRoot, 'LogicApp');
expect(config.extraInitializeData).toEqual({
workspaceFileJson,
logicAppsWithoutCustomCode,
logicAppsWithoutCustomCode: [{ label: 'LogicApp', description: expectedLogicAppPath, data: expectedLogicAppPath }],
existingFolders: ['LogicApp', 'CSharpProject'],
workspaceRootFolder: workspaceRoot,
});
expect(config.dialogOptions?.workspace).toMatchObject({
canSelectMany: false,
Expand All @@ -166,7 +172,7 @@ describe('workspace webview command wrappers', () => {
});

it('getExistingFoldersOnDisk filters out non-directory entries using FileType mock', async () => {
const workspaceFile = { fsPath: 'D:\\workspace\\MyWorkspace.code-workspace' };
const workspaceFile = { fsPath: path.join(workspaceRoot, 'MyWorkspace.code-workspace') };
const workspaceFileJson = { folders: [{ path: './LogicApp' }] };
(vscode.workspace as any).workspaceFile = workspaceFile;
(vscode.workspace.fs.readFile as Mock).mockResolvedValue(Buffer.from(JSON.stringify(workspaceFileJson)));
Expand All @@ -185,6 +191,7 @@ describe('workspace webview command wrappers', () => {
workspaceFileJson,
logicAppsWithoutCustomCode: [],
existingFolders: ['LogicApp', 'AnotherProject'],
workspaceRootFolder: workspaceRoot,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ vi.mock('../cloudToLocal/cloudToLocal', () => ({ cloudToLocal: vi.fn() }));
vi.mock('../createProject/createProject', () => ({ createProject: vi.fn() }));
vi.mock('../createWorkspace/createWorkspace', () => ({ createWorkspace: vi.fn() }));
vi.mock('../createCustomCodeFunction/createCustomCodeFunction', () => ({ createCustomCodeFunction: vi.fn() }));
vi.mock('../addCustomCode/addCustomCode', () => ({ addCustomCode: vi.fn() }));
vi.mock('../createSlot', () => ({ createSlot: vi.fn() }));
vi.mock('../createWorkflow/createWorkflow', () => ({ createWorkflow: vi.fn() }));
vi.mock('../dataMapper/dataMapper', () => ({ createDataMap: vi.fn(), loadDataMapFile: vi.fn() }));
Expand Down Expand Up @@ -195,6 +196,12 @@ describe('registerCommands', () => {
expect(totalRegistrations).toBeGreaterThan(40);
});

it('should register the addCustomCode command', () => {
registerCommands();
const registeredCommands = mockRegisterCommand.mock.calls.map((call: any[]) => call[0]);
expect(registeredCommands).toContain('azureLogicAppsStandard.addCustomCode');
});

describe('error handler', () => {
it('should suppress report issue button for all errors', () => {
registerCommands();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { IActionContext } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';

// Hoisted mocks
const {
mockIsLogicAppProject,
mockHasCodefulSdkReference,
mockTryGetLogicAppCustomCodeFunctionsProjects,
mockCreateWorkspaceWebviewCommandHandler,
mockShowErrorMessage,
mockCallWithTelemetryAndErrorHandling,
} = vi.hoisted(() => ({
mockIsLogicAppProject: vi.fn(),
mockHasCodefulSdkReference: vi.fn(),
mockTryGetLogicAppCustomCodeFunctionsProjects: vi.fn(),
mockCreateWorkspaceWebviewCommandHandler: vi.fn(),
mockShowErrorMessage: vi.fn(),
mockCallWithTelemetryAndErrorHandling: vi.fn(async (_id: string, cb: any) =>
cb({
telemetry: { properties: {} },
errorHandling: {},
})
),
}));

vi.mock('vscode', () => ({
window: {
showErrorMessage: mockShowErrorMessage,
},
workspace: {
workspaceFile: undefined as vscode.Uri | undefined,
fs: {
readFile: vi.fn(),
readDirectory: vi.fn().mockResolvedValue([]),
},
},
Uri: {
file: (p: string) => ({ fsPath: p, path: p, scheme: 'file' }),
},
FileType: { Directory: 2 },
}));

vi.mock('../../../utils/verifyIsProject', () => ({
isLogicAppProject: mockIsLogicAppProject,
}));

vi.mock('../../../utils/codeful', () => ({
hasCodefulSdkReference: mockHasCodefulSdkReference,
}));

vi.mock('../../../utils/customCodeUtils', () => ({
tryGetLogicAppCustomCodeFunctionsProjects: mockTryGetLogicAppCustomCodeFunctionsProjects,
}));

vi.mock('../../shared/workspaceWebviewCommandHandler', () => ({
createWorkspaceWebviewCommandHandler: mockCreateWorkspaceWebviewCommandHandler,
}));

vi.mock('../../createNewCodeProject/CodeProjectBase/CreateLogicAppProjects', () => ({
createLogicAppProject: vi.fn(),
}));

vi.mock('../../../../localize', () => ({
localize: (_key: string, msg: string, ...args: any[]) => {
let result = msg;
args.forEach((a, i) => {
result = result.replace(`{${i}}`, String(a));
});
return result;
},
}));

vi.mock('../../../../extensionVariables', () => ({
ext: {
outputChannel: { appendLog: vi.fn() },
webViewKey: { createLogicApp: 'createLogicApp' },
extensionVersion: '1.0.0',
context: { extensionPath: '/mock' },
},
}));

vi.mock('../../../../constants', () => ({
extensionContext: {
customCodeFunctionsFolders: 'azureLogicAppsStandard.customCode.functionsFolders',
customCodeEligibleLogicAppFolders: 'azureLogicAppsStandard.customCode.eligibleLogicAppFolders',
},
}));

vi.mock('@microsoft/vscode-azext-utils', () => ({
callWithTelemetryAndErrorHandling: mockCallWithTelemetryAndErrorHandling,
AzureWizardPromptStep: vi.fn(),
AzureWizardExecuteStep: vi.fn(),
AzureWizard: class {
async prompt() {}
async execute() {}
},
registerCommand: vi.fn(),
registerCommandWithTreeNodeUnwrapping: vi.fn(),
registerErrorHandler: vi.fn(),
registerReportIssueCommand: vi.fn(),
unwrapTreeNodeCommandCallback: vi.fn(),
parseError: vi.fn(() => ({ message: 'mock error' })),
UserCancelledError: class extends Error {},
nonNullProp: vi.fn(),
nonNullValue: vi.fn(),
nonNullOrEmptyValue: vi.fn((v: any) => v),
DialogResponses: vi.fn(),
AzExtTreeItem: class {},
AzExtParentTreeItem: class {},
openUrl: vi.fn(),
}));

import { addCustomCode } from '../addCustomCode';

describe('addCustomCode', () => {
const createContext = (): IActionContext =>
({
telemetry: { properties: {}, measurements: {} },
errorHandling: {},
ui: {},
valuesToMask: [],
}) as unknown as IActionContext;

beforeEach(() => {
vi.clearAllMocks();
mockIsLogicAppProject.mockResolvedValue(false);
mockHasCodefulSdkReference.mockResolvedValue(false);
mockTryGetLogicAppCustomCodeFunctionsProjects.mockResolvedValue(undefined);
});

it('should show error when no URI is provided', async () => {
await addCustomCode(createContext(), undefined);
expect(mockShowErrorMessage).toHaveBeenCalledWith(expect.stringContaining('Explorer context menu'));
expect(mockCreateWorkspaceWebviewCommandHandler).not.toHaveBeenCalled();
});

it('should show error when folder is not a Logic App project', async () => {
mockIsLogicAppProject.mockResolvedValue(false);
const uri = vscode.Uri.file('/workspace/notALogicApp');
await addCustomCode(createContext(), uri);
expect(mockShowErrorMessage).toHaveBeenCalledWith(expect.stringContaining('not a Logic App project'));
expect(mockCreateWorkspaceWebviewCommandHandler).not.toHaveBeenCalled();
});

it('should show error when folder is a codeful project', async () => {
mockIsLogicAppProject.mockResolvedValue(true);
mockHasCodefulSdkReference.mockResolvedValue(true);
const uri = vscode.Uri.file('/workspace/codefulApp');
await addCustomCode(createContext(), uri);
expect(mockShowErrorMessage).toHaveBeenCalledWith(expect.stringContaining('.NET SDK project'));
expect(mockCreateWorkspaceWebviewCommandHandler).not.toHaveBeenCalled();
});

it('should show error when custom code already exists', async () => {
mockIsLogicAppProject.mockResolvedValue(true);
mockHasCodefulSdkReference.mockResolvedValue(false);
mockTryGetLogicAppCustomCodeFunctionsProjects.mockResolvedValue(['/workspace/MyFunctions']);
const uri = vscode.Uri.file('/workspace/myLogicApp');
await addCustomCode(createContext(), uri);
expect(mockShowErrorMessage).toHaveBeenCalledWith(expect.stringContaining('already has an associated custom code project'));
expect(mockCreateWorkspaceWebviewCommandHandler).not.toHaveBeenCalled();
});

it('should show error when no workspace file is open', async () => {
mockIsLogicAppProject.mockResolvedValue(true);
mockHasCodefulSdkReference.mockResolvedValue(false);
mockTryGetLogicAppCustomCodeFunctionsProjects.mockResolvedValue([]);
(vscode.workspace as any).workspaceFile = undefined;
const uri = vscode.Uri.file('/workspace/myLogicApp');
await addCustomCode(createContext(), uri);
expect(mockShowErrorMessage).toHaveBeenCalledWith(expect.stringContaining('.code-workspace'));
expect(mockCreateWorkspaceWebviewCommandHandler).not.toHaveBeenCalled();
});

it('should open wizard with pre-configured custom code data when all validations pass', async () => {
mockIsLogicAppProject.mockResolvedValue(true);
mockHasCodefulSdkReference.mockResolvedValue(false);
mockTryGetLogicAppCustomCodeFunctionsProjects.mockResolvedValue([]);
const workspaceUri = vscode.Uri.file('/workspace/myWorkspace.code-workspace');
(vscode.workspace as any).workspaceFile = workspaceUri;
(vscode.workspace.fs.readFile as any).mockResolvedValue(Buffer.from(JSON.stringify({ folders: [{ path: 'myLogicApp' }] })));
(vscode.workspace.fs.readDirectory as any).mockResolvedValue([['myLogicApp', vscode.FileType.Directory]]);

const uri = vscode.Uri.file('/workspace/myLogicApp');
await addCustomCode(createContext(), uri);

expect(mockCreateWorkspaceWebviewCommandHandler).toHaveBeenCalledTimes(1);
const config = mockCreateWorkspaceWebviewCommandHandler.mock.calls[0][0];
expect(config.extraInitializeData.isAddCustomCodeFlow).toBe(true);
expect(config.extraInitializeData.preselectedLogicAppName).toBe('myLogicApp');
expect(config.extraInitializeData.preselectedLogicAppType).toBe('customCode');
expect(config.extraInitializeData.logicAppsWithoutCustomCode).toEqual([expect.objectContaining({ label: 'myLogicApp' })]);
});
});
Loading
Loading