diff --git a/test/global.test.ts b/test/global.test.ts index d7b6516ce..49ef2aa78 100644 --- a/test/global.test.ts +++ b/test/global.test.ts @@ -27,7 +27,10 @@ let oldRequestTimeout: number | undefined; let testWorkspaceFolders: string[]; let workspaceFolderIndex = 0; -export function getTestWorkspaceFolder(): string { +export function getTestWorkspaceFolder(folder?: string): string { + if (folder) { + return testWorkspaceFolders.find(f => f.toLowerCase().includes(folder.toLowerCase())) || testWorkspaceFolders[0]; + } if (workspaceFolderIndex >= testWorkspaceFolders.length) { throw new Error('Not enough workspace folders. Add more in "test/test.code-workspace".') } @@ -170,6 +173,12 @@ async function initTestWorkspaceFolders(): Promise { for (let i = 0; i < workspaceFolders.length; i++) { const workspacePath: string = workspaceFolders[i].uri.fsPath; const folderName = path.basename(workspacePath); + if (folderName === 'containerizedFunctionProject') { + await AzExtFsExtra.ensureDir(workspacePath); + await AzExtFsExtra.emptyDir(workspacePath); + folders.push(workspacePath); + continue; + } assert.equal(folderName, String(i), `Unexpected workspace folder name "${folderName}".`); await AzExtFsExtra.ensureDir(workspacePath); await AzExtFsExtra.emptyDir(workspacePath); diff --git a/test/project/createAndValidateProject.ts b/test/project/createAndValidateProject.ts index b688db9b9..6ae637cac 100644 --- a/test/project/createAndValidateProject.ts +++ b/test/project/createAndValidateProject.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { type TestActionContext, type TestInput } from '@microsoft/vscode-azext-dev'; +import { AzExtFsExtra } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; import { ProjectLanguage, createNewProjectInternal, getRandomHexString, hiddenStacksSetting } from '../../extension.bundle'; // eslint-disable-next-line no-restricted-imports +import { CreateDockerfileProjectStep } from '../../src/commands/createNewProject/dockerfileSteps/CreateDockerfileProjectStep'; +// eslint-disable-next-line no-restricted-imports import type * as api from '../../src/vscode-azurefunctions.api'; -import { testFolderPath } from '../global.test'; +import { getTestWorkspaceFolder, testFolderPath } from '../global.test'; import { runWithFuncSetting } from '../runWithSetting'; -import { validateProject, type IValidateProjectOptions } from './validateProject'; +import { validateContainerizedProject, validateProject, type IValidateProjectOptions } from './validateProject'; export interface ICreateProjectTestOptions extends IValidateProjectOptions { isHiddenLanguage?: boolean; @@ -50,3 +53,32 @@ export async function createAndValidateProject(context: TestActionContext, optio await validateProject(projectPath, options); } + +export async function createAndValidateContainerizedProject(context: TestActionContext, options: ICreateProjectTestOptions): Promise { + // Clone inputs here so we have a different array each time + const inputs: (string | TestInput | RegExp)[] = options.inputs ? [...options.inputs] : []; + const language: ProjectLanguage = options.language; + const projectPath: string = getTestWorkspaceFolder('containerizedFunctionProject'); + //Empty folder for next test + await AzExtFsExtra.emptyDir(projectPath); + + if (!options.isHiddenLanguage) { + inputs.unshift(options.displayLanguage || language); + } + + inputs.unshift('containerizedFunctionProject'); + + await runWithFuncSetting(hiddenStacksSetting, true, async () => { + await context.ui.runWithInputs(inputs, async () => { + await createNewProjectInternal(context, + { + executeStep: new CreateDockerfileProjectStep(), + languageFilter: /Python|C\#|(Java|Type)Script|PowerShell$/i, + version: options.version, + suppressOpenFolder: true + }); + }); + }); + + await validateContainerizedProject(projectPath, options); +} diff --git a/test/project/createNewContainerizedDotenetProject.test.ts b/test/project/createNewContainerizedDotenetProject.test.ts new file mode 100644 index 000000000..aead1fda0 --- /dev/null +++ b/test/project/createNewContainerizedDotenetProject.test.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { runWithTestActionContext } from "@microsoft/vscode-azext-dev"; +import { FuncVersion, getRandomHexString } from "../../extension.bundle"; +import { getRotatingAuthLevel } from "../nightly/getRotatingValue"; +import { createAndValidateContainerizedProject } from "./createAndValidateProject"; +import { getCSharpValidateOptions } from "./validateProject"; + +suite('Create New Dotnet Project', () => { + test('checkDockerfileDotnetIsolated', async () => { + const functionName: string = 'func' + getRandomHexString(); + const input = [/7.*isolated/i, /http\s*trigger/i, functionName, 'Company.Function', getRotatingAuthLevel()] + await runWithTestActionContext('createProject', async context => { + await createAndValidateContainerizedProject(context, { ...getCSharpValidateOptions('net7.0', FuncVersion.v4), inputs: input }) + }); + }); + + test('checkDockerfileDotnetLTS', async () => { + const functionName: string = 'func' + getRandomHexString(); + const input = [/6/i, /http\s*trigger/i, functionName, 'Company.Function', getRotatingAuthLevel()] + await runWithTestActionContext('createProject', async context => { + await createAndValidateContainerizedProject(context, { ...getCSharpValidateOptions('net6.0', FuncVersion.v4), inputs: input }) + }); + }); +}); + diff --git a/test/project/validateProject.ts b/test/project/validateProject.ts index f10e4c309..00c25f659 100644 --- a/test/project/validateProject.ts +++ b/test/project/validateProject.ts @@ -8,7 +8,10 @@ import * as assert from 'assert'; import * as fse from 'fs-extra'; import * as globby from 'globby'; import * as path from 'path'; +import * as semver from 'semver'; import { FuncVersion, JavaBuildTool, ProjectLanguage, extensionId, getContainingWorkspace, type IExtensionsJson, type ILaunchJson, type ITasksJson } from '../../extension.bundle'; +// eslint-disable-next-line no-restricted-imports +import { detectFunctionsDockerfile } from '../../src/commands/createFunctionApp/containerImage/detectDockerfile'; export const defaultTestFuncVersion: FuncVersion = FuncVersion.v4; @@ -425,3 +428,33 @@ export async function validateProject(projectPath: string, options: IValidatePro const gitignoreContents: string = (await fse.readFile(path.join(projectPath, '.gitignore'))).toString(); assert.equal(gitignoreContents.indexOf('.vscode'), -1, 'The ".vscode" folder is being ignored.'); } + +export async function validateContainerizedProject(projectPath: string, options: IValidateProjectOptions): Promise { + // + // Validate dockerfile is functions dockerfile + const expectedPaths: ExpectedPath[] = getCommonExpectedPaths(projectPath, options.workspaceFolder).filter(p1 => !options.excludedPaths || !options.excludedPaths.find(p2 => p1 === p2)); + expectedPaths.push('Dockerfile'); + if (expectedPaths.find(p => typeof p === 'string' && p.includes('Dockerfile'))) { + assert.ok(await AzExtFsExtra.pathExists(path.join(projectPath, 'Dockerfile')), 'Dockerfile does not exist.'); + const dockerfileFound = await detectFunctionsDockerfile(path.join(projectPath, 'Dockerfile')); + assert.ok(dockerfileFound, 'Dockerfile does not contain the expected functions image.'); + } + + //validate csproj version matches dockerfile type + const dockerfileContents: string = (await fse.readFile(path.join(projectPath, 'Dockerfile'))).toString(); + const dockerfileLines: string[] = dockerfileContents.split('\n'); + const csprojContents: string = (await fse.readFile(path.join(projectPath, 'containerizedFunctionProject.csproj'))).toString(); + const csprojLines: string[] = csprojContents.split('\n'); + for (const line of dockerfileLines) { + if (line.includes('dotnet-isolated')) { + const azureFunctionsWorkerVersion = csprojLines.find(l => l.includes('Include=\"Microsoft.Azure.Functions.Worker\"'))?.match(/Version=\"(\d+\.\d+\.\d+)\"/)?.[1]; + assert.ok(azureFunctionsWorkerVersion, 'csproj does not contain a Microsoft.Azure.Functions.Worker version.'); + assert.ok(semver.satisfies(azureFunctionsWorkerVersion, '1.20.x'), 'csproj does not contain the expected Microsoft.Azure.Functions.Worker version.'); + } else if (line.includes('dotnet:4')) { + const netSdkFunctionsVersion = csprojLines.find(l => l.includes('Include=\"Microsoft.NET.Sdk.Functions\"'))?.match(/Version=\"(\d+\.\d+\.\d+)\"/)?.[1]; + assert.ok(netSdkFunctionsVersion, 'csproj does not contain an sdk package.'); + assert.ok(semver.satisfies(netSdkFunctionsVersion, '4.x.x'), 'csproj does not contain the expected sdk package.'); + assert.ok(csprojLines.some(l => l.includes('Include=\"Microsoft.NET.Sdk.Functions\" Version=\"4.2.0\"')), 'csproj does not contain the expected sdk package.'); + } + } +} diff --git a/test/test.code-workspace b/test/test.code-workspace index 26a370e25..5cc6c5a3a 100644 --- a/test/test.code-workspace +++ b/test/test.code-workspace @@ -29,6 +29,9 @@ }, { "path": "../testWorkspace/9" + }, + { + "path": "../testWorkspace/containerizedFunctionProject" } ], "settings": {