vscode-azurefunctions/test/project/initProjectForVSCode.test.ts

386 строки
17 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { runWithTestActionContext, type TestInput } from '@microsoft/vscode-azext-dev';
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { FuncVersion, getRandomHexString, initProjectForVSCode, JavaBuildTool, ProjectLanguage } from '../../extension.bundle';
import { cleanTestWorkspace, testFolderPath } from '../global.test';
import { getBallerinaValidateOptions, getCSharpValidateOptions, getCustomValidateOptions, getFSharpValidateOptions, getJavaScriptValidateOptions, getJavaValidateOptions, getPowerShellValidateOptions, getPythonValidateOptions, getTypeScriptValidateOptions, validateProject, type IValidateProjectOptions } from './validateProject';
suite('Init Project For VS Code', function (this: Mocha.Suite): void {
this.timeout(30 * 1000);
suiteSetup(async () => {
await cleanTestWorkspace();
});
test('JavaScript', async () => {
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles: [['HttpTriggerJs', 'index.js']] });
});
test('JavaScript with package.json', async () => {
await initAndValidateProject({ ...getJavaScriptValidateOptions(true /* hasPackageJson */), mockFiles: [['HttpTriggerJs', 'index.js'], 'package.json'] });
});
test('JavaScript with extensions.csproj', async () => {
const options: IValidateProjectOptions = getJavaScriptValidateOptions(true /* hasPackageJson */);
options.expectedSettings['files.exclude'] = { obj: true, bin: true };
await initAndValidateProject({ ...options, mockFiles: [['HttpTriggerJs', 'index.js'], 'package.json', 'extensions.csproj'] });
});
test('TypeScript', async () => {
await initAndValidateProject({ ...getTypeScriptValidateOptions({ missingCleanScript: true }), mockFiles: [['HttpTrigger', 'index.ts'], 'tsconfig.json', 'package.json'] });
});
test('TypeScript with extensions.csproj', async () => {
const options: IValidateProjectOptions = getTypeScriptValidateOptions({ missingCleanScript: true });
options.expectedSettings['files.exclude'] = { obj: true, bin: true };
await initAndValidateProject({ ...options, mockFiles: [['HttpTrigger', 'index.ts'], 'tsconfig.json', 'package.json', 'extensions.csproj'] });
});
test('C#', async () => {
const mockFiles: MockFile[] = [{ fsPath: 'test.csproj', contents: '<TargetFramework>netstandard2.0<\/TargetFramework><AzureFunctionsVersion>v2</AzureFunctionsVersion>' }];
await initAndValidateProject({ ...getCSharpValidateOptions('netstandard2.0', FuncVersion.v2), mockFiles });
});
test('C# with extensions.csproj', async () => {
const mockFiles: MockFile[] = ['extensions.csproj', { fsPath: 'test.csproj', contents: '<TargetFramework>netstandard2.0<\/TargetFramework><AzureFunctionsVersion>v2</AzureFunctionsVersion>' }];
await initAndValidateProject({ ...getCSharpValidateOptions('netstandard2.0', FuncVersion.v2, 2), mockFiles });
});
function getMockVenvPath(venvName: string): MockFilePath {
return process.platform === 'win32' ? [venvName, 'Scripts', 'activate'] : [venvName, 'bin', 'activate'];
}
test('Python no venv', async () => {
const mockFiles: MockFile[] = [['HttpTrigger', '__init__.py'], 'requirements.txt'];
await initAndValidateProject({ ...getPythonValidateOptions(undefined), mockFiles, inputs: [/skip/i] });
});
test('Python single venv', async () => {
const venvName: string = 'testEnv';
const mockFiles: MockFile[] = [['HttpTrigger', '__init__.py'], 'requirements.txt', getMockVenvPath(venvName)];
await initAndValidateProject({ ...getPythonValidateOptions(venvName), mockFiles });
});
test('Python multiple venvs', async () => {
const venvName: string = 'world';
const mockFiles: MockFile[] = [['HttpTrigger', '__init__.py'], 'requirements.txt', getMockVenvPath('hello'), getMockVenvPath(venvName)];
await initAndValidateProject({ ...getPythonValidateOptions(venvName), mockFiles, inputs: [venvName] });
});
test('Python with extensions.csproj', async () => {
const venvName: string = 'testEnv';
const options: IValidateProjectOptions = getPythonValidateOptions(venvName);
options.expectedTasks.push('extensions install');
options.expectedSettings['files.exclude'] = { obj: true, bin: true };
options.expectedSettings['azureFunctions.preDeployTask'] = 'func: extensions install';
const mockFiles: MockFile[] = [['HttpTrigger', '__init__.py'], 'requirements.txt', getMockVenvPath(venvName), 'extensions.csproj'];
await initAndValidateProject({ ...options, mockFiles });
});
test('F#', async () => {
const mockFiles: MockFile[] = [{ fsPath: 'test.fsproj', contents: '<TargetFramework>netstandard2.0<\/TargetFramework><AzureFunctionsVersion>v2</AzureFunctionsVersion>' }];
await initAndValidateProject({ ...getFSharpValidateOptions('netstandard2.0', FuncVersion.v2), mockFiles });
});
test('Java - Maven', async () => {
const appName: string = 'javaApp1';
const mockFiles: MockFile[] = [
{
fsPath: 'pom.xml',
contents: `<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<functionAppName>${appName}</functionAppName>
</properties>
</project>`
},
{ fsPath: 'src', isDir: true }
];
await initAndValidateProject({ ...getJavaValidateOptions(appName, JavaBuildTool.maven), mockFiles });
});
test('Java - Gradle', async () => {
const appName: string = 'javaApp1';
const mockFiles: MockFile[] = [
{
fsPath: 'build.gradle',
contents: `azurefunctions {
appName = '${appName}'
}`
}
];
await initAndValidateProject({ ...getJavaValidateOptions(appName, JavaBuildTool.gradle), mockFiles });
});
test('Ballerina', async () => {
const mockFiles: MockFile[] = [
{
fsPath: 'Ballerina.toml',
contents: `[package]
org = "testorg"
name = "azf_test"
version = "0.1.0"`
}
];
await initAndValidateProject({ ...getBallerinaValidateOptions(), mockFiles });
});
test('PowerShell', async () => {
await initAndValidateProject({ ...getPowerShellValidateOptions(), mockFiles: [['HttpTriggerPS', 'run.ps1'], 'profile.ps1', 'requirements.psd1'] });
});
test('PowerShell with extensions.csproj', async () => {
const options: IValidateProjectOptions = getPowerShellValidateOptions();
options.expectedSettings['files.exclude'] = { obj: true, bin: true };
options.expectedSettings['azureFunctions.preDeployTask'] = 'func: extensions install';
await initAndValidateProject({ ...options, mockFiles: [['HttpTriggerPS', 'run.ps1'], 'profile.ps1', 'requirements.psd1', 'extensions.csproj'] });
});
test('Custom', async () => {
const mockFiles: MockFile[] = [{
fsPath: 'local.settings.json',
contents: {
IsEncrypted: false,
Values: {
FUNCTIONS_WORKER_RUNTIME: "custom",
AzureWebJobsStorage: ""
}
}
}];
await initAndValidateProject({ ...getCustomValidateOptions(), mockFiles });
});
test('Multi-language', async () => {
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles: [['HttpTriggerTS', 'index.ts'], ['HttpTriggerCSX', 'run.csx']], inputs: [ProjectLanguage.JavaScript] });
});
test('Multi-function', async () => {
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles: [['HttpTriggerJS1', 'index.js'], ['HttpTriggerJS2', 'index.js']] });
});
test('Existing extensions.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'extensions.json'], contents: { recommendations: ["testid"] } }];
const options: IInitProjectTestOptions = { ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] };
options.expectedExtensionRecs.push('testid');
await initAndValidateProject(options);
});
test('Invalid extensions.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'extensions.json'], contents: '{' }];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Existing settings.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'settings.json'], contents: { "azureFunctions.testSetting": "testValue" } }];
const options: IInitProjectTestOptions = { ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] };
options.expectedSettings['azureFunctions.testSetting'] = 'testValue';
await initAndValidateProject(options);
});
test('Invalid settings.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'settings.json'], contents: '{' }];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Existing tasks.json', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'tasks.json'],
contents: {
version: "2.0.0",
tasks: [
{
label: "hello world",
command: "echo 'hello world'",
type: "shell"
}
]
}
}];
const options: IInitProjectTestOptions = { ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] };
options.expectedTasks.push('hello world');
await initAndValidateProject(options);
});
test('Invalid tasks.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'tasks.json'], contents: '{' }];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Overwrite existing task', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'tasks.json'],
contents: {
version: "2.0.0",
tasks: [
{
type: "func",
command: "host start"
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Overwrite'] });
});
test('Does not prompt to overwrite existing, identical task', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'tasks.json'],
contents: {
version: "2.0.0",
tasks: [
{
type: "func",
command: "host start",
problemMatcher: "$func-node-watch",
isBackground: true
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] });
});
test('Overwrite multiple tasks', async () => {
const mockFiles: MockFile[] = ['package.json', {
fsPath: ['.vscode', 'tasks.json'],
contents: {
version: "2.0.0",
tasks: [
{
type: "func",
command: "host start"
},
{
type: "shell",
label: "npm install (functions)",
command: "whoops"
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(true /* hasPackageJson */), mockFiles, inputs: ['Overwrite'] });
});
test('Old tasks.json', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'tasks.json'],
contents: {
version: "1.0.0",
tasks: [
{
label: "hello world",
command: "echo 'hello world'",
type: "shell"
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Existing launch.json', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'launch.json'],
contents: {
version: "0.2.0",
configurations: [
{
name: "Launch 1",
request: "attach",
type: "node"
}
]
}
}];
const options: IInitProjectTestOptions = { ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] };
options.expectedDebugConfigs.push('Launch 1');
await initAndValidateProject(options);
});
test('Invalid launch.json', async () => {
const mockFiles: MockFile[] = [{ fsPath: ['.vscode', 'launch.json'], contents: '{' }];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Overwrite existing debug config', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'launch.json'],
contents: {
version: "0.2.0",
configurations: [
{
name: "Attach to Node Functions",
type: "node",
request: "attach"
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] });
});
test('Old launch.json', async () => {
const mockFiles: MockFile[] = [{
fsPath: ['.vscode', 'launch.json'],
contents: {
version: "0.1.0",
configurations: [
{
name: "Launch 1",
request: "attach",
type: "node"
}
]
}
}];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript, 'Yes'] });
});
test('Invalid gitignore', async () => {
const mockFiles: MockFile[] = [{ fsPath: '.gitignore', contents: '.vscode' }];
await initAndValidateProject({ ...getJavaScriptValidateOptions(), mockFiles, inputs: [ProjectLanguage.JavaScript] });
});
});
type MockFilePath = string | string[];
type MockFile = MockFilePath | { fsPath: MockFilePath; contents?: string | object; isDir?: boolean };
interface IInitProjectTestOptions extends IValidateProjectOptions {
mockFiles?: MockFile[];
inputs?: (string | RegExp | TestInput)[];
}
async function initAndValidateProject(options: IInitProjectTestOptions): Promise<void> {
const projectPath: string = path.join(testFolderPath, getRandomHexString());
const mockFiles: MockFile[] = options.mockFiles || [];
mockFiles.push('local.settings.json', 'host.json', '.funcignore', '.gitignore', { fsPath: '.git', isDir: true });
await Promise.all(mockFiles.map(async mockFile => {
mockFile = typeof mockFile === 'string' || Array.isArray(mockFile) ? { fsPath: mockFile } : mockFile;
const subPaths: string[] = typeof mockFile.fsPath === 'string' ? [mockFile.fsPath] : mockFile.fsPath;
const fullPath: string = path.join(projectPath, ...subPaths);
mockFile.isDir ? await AzExtFsExtra.ensureDir(fullPath) : await AzExtFsExtra.ensureFile(fullPath);
if (typeof mockFile.contents === 'object') {
await AzExtFsExtra.writeJSON(fullPath, mockFile.contents);
} else if (mockFile.contents) {
await AzExtFsExtra.writeFile(fullPath, mockFile.contents);
}
}));
await runWithTestActionContext('initProject', async context => {
await context.ui.runWithInputs(options.inputs || [], async () => {
await initProjectForVSCode(context, projectPath);
});
});
await validateProject(projectPath, options);
}