mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 17:22:42 -05:00
Adds autorest-based SQL Project generation to SQL Database Projects extension (#17078)
* Initial changes * checkpoint * Constructing project with post deployment script * Correcting to intentionally read from cached list of projects * Adding activation event, fixing fresh workspace bug * Convert netcoreTool and autorestHelper to share a helper class for streamed command * Include npm package version to force update * test checkpoint * Unit tests * Added contextual quickpicks for autorest dialogs * Adding projectController test * Added projectController test, some refactoring for testability * Merge branch 'main' into benjin/autorest * Fixing 'which' import * PR feedback * Fixing tests * Adding additional information for when project provider tests fail * Hopefully fixing failing tests (unable to repro locally) * Adding Generate Project item to workspace menu * PR feedback
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as testUtils from './testUtils';
|
||||
import * as utils from '../common/utils';
|
||||
import * as path from 'path';
|
||||
import { TestContext, createContext } from './testContext';
|
||||
import { AutorestHelper } from '../tools/autorestHelper';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
let testContext: TestContext;
|
||||
|
||||
describe('Autorest tests', function (): void {
|
||||
beforeEach(function (): void {
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should detect autorest', async function (): Promise<void> {
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const executable = await autorestHelper.detectInstallation();
|
||||
should(executable === 'autorest' || executable === 'npx autorest').equal(true, 'autorest command should be found in default path during unit tests');
|
||||
});
|
||||
|
||||
it('Should run an autorest command successfully', async function (): Promise<void> {
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const dummyFile = path.join(await testUtils.generateTestFolderPath(), 'testoutput.log');
|
||||
sinon.stub(autorestHelper, 'constructAutorestCommand').returns(`${await autorestHelper.detectInstallation()} --version > ${dummyFile}`);
|
||||
|
||||
try {
|
||||
await autorestHelper.generateAutorestFiles('fakespec.yaml', 'fakePath');
|
||||
const text = (await fs.readFile(dummyFile)).toString().trim();
|
||||
const expected = 'AutoRest code generation utility';
|
||||
should(text.includes(expected)).equal(true, `Substring not found. Expected "${expected}" in "${text}"`);
|
||||
} finally {
|
||||
if (await utils.exists(dummyFile)) {
|
||||
await fs.unlink(dummyFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('Should construct a correct autorest command for project generation', async function (): Promise<void> {
|
||||
const expectedOutput = 'autorest --use:autorest-sql-testing@latest --input-file="/some/path/test.yaml" --output-folder="/some/output/path" --clear-output-folder';
|
||||
|
||||
const autorestHelper = new AutorestHelper(testContext.outputChannel);
|
||||
const constructedCommand = autorestHelper.constructAutorestCommand((await autorestHelper.detectInstallation())!, '/some/path/test.yaml', '/some/output/path');
|
||||
|
||||
// depending on whether the machine running the test has autorest installed or just node, the expected output may differ by just the prefix, hence matching against two options
|
||||
should(constructedCommand === expectedOutput || constructedCommand === `npx ${expectedOutput}`).equal(true, `Constructed autorest command not formatting as expected:\nActual: ${constructedCommand}\nExpected: [npx ]${expectedOutput}`);
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProje
|
||||
import { ProjectsController } from '../controllers/projectController';
|
||||
import { promises as fs } from 'fs';
|
||||
import { createContext, TestContext, mockDacFxResult, mockConnectionProfile } from './testContext';
|
||||
import { Project, reservedProjectFolders, SystemDatabase, FileProjectEntry, SystemDatabaseReferenceProjectEntry } from '../models/project';
|
||||
import { Project, reservedProjectFolders, SystemDatabase, FileProjectEntry, SystemDatabaseReferenceProjectEntry, EntryType } from '../models/project';
|
||||
import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
|
||||
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
||||
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
|
||||
@@ -429,7 +429,7 @@ describe('ProjectsController', function (): void {
|
||||
|
||||
const proj = await testUtils.createTestProject(baselines.openProjectFileBaseline);
|
||||
|
||||
await projController.object.publishOrScriptProject(proj, { connectionUri: '', databaseName: '' , serverName: ''}, false);
|
||||
await projController.object.publishOrScriptProject(proj, { connectionUri: '', databaseName: '', serverName: '' }, false);
|
||||
|
||||
should(builtDacpacPath).not.equal('', 'built dacpac path should be set');
|
||||
should(publishedDacpacPath).not.equal('', 'published dacpac path should be set');
|
||||
@@ -665,7 +665,7 @@ describe('ProjectsController', function (): void {
|
||||
// add dacpac reference to something in the a folder outside of the project
|
||||
await projController.addDatabaseReferenceCallback(project1, {
|
||||
databaseName: <string>this.databaseNameTextbox?.value,
|
||||
dacpacFileLocation: vscode.Uri.file(path.join(path.dirname(projFilePath), '..','someFolder', 'outsideFolderTest.dacpac')),
|
||||
dacpacFileLocation: vscode.Uri.file(path.join(path.dirname(projFilePath), '..', 'someFolder', 'outsideFolderTest.dacpac')),
|
||||
suppressMissingDependenciesErrors: false
|
||||
},
|
||||
{ treeDataProvider: new SqlDatabaseProjectTreeViewProvider(), element: undefined });
|
||||
@@ -678,6 +678,49 @@ describe('ProjectsController', function (): void {
|
||||
should(projFileText).containEql('..\\someFolder\\outsideFolderTest.dacpac');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AutoRest generation', function (): void {
|
||||
it('Should create project from autorest-generated files', async function (): Promise<void> {
|
||||
const parentFolder = await testUtils.generateTestFolderPath();
|
||||
await testUtils.createDummyFileStructure();
|
||||
const specName = 'DummySpec.yaml';
|
||||
const newProjFolder = path.join(parentFolder, path.basename(specName, '.yaml'));
|
||||
let fileList: vscode.Uri[] = [];
|
||||
|
||||
const projController = TypeMoq.Mock.ofType(ProjectsController);
|
||||
projController.callBase = true;
|
||||
|
||||
projController.setup(x => x.selectAutorestSpecFile()).returns(async () => specName);
|
||||
projController.setup(x => x.selectAutorestProjectLocation(TypeMoq.It.isAny())).returns(async () => {
|
||||
await fs.mkdir(newProjFolder);
|
||||
|
||||
return {
|
||||
newProjectFolder: newProjFolder,
|
||||
outputFolder: parentFolder,
|
||||
projectName: path.basename(specName, '.yaml')
|
||||
};
|
||||
});
|
||||
|
||||
projController.setup(x => x.generateAutorestFiles(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => {
|
||||
await testUtils.createDummyFileStructure(true, fileList, newProjFolder);
|
||||
await testUtils.createTestFile('SELECT \'This is a post-deployment script\'', constants.autorestPostDeploymentScriptName, newProjFolder);
|
||||
});
|
||||
|
||||
projController.setup(x => x.openProjectInWorkspace(TypeMoq.It.isAny())).returns(async () => { });
|
||||
|
||||
const project = (await projController.object.generateProjectFromOpenApiSpec())!;
|
||||
|
||||
should(project.postDeployScripts.length).equal(1, `Expected 1 post-deployment script, got ${project?.postDeployScripts.length}`);
|
||||
const actual = path.basename(project.postDeployScripts[0].fsUri.fsPath);
|
||||
should(actual).equal(constants.autorestPostDeploymentScriptName, `Unexpected post-deployment script name: ${actual}, expected ${constants.autorestPostDeploymentScriptName}`);
|
||||
|
||||
const expectedScripts = fileList.filter(f => path.extname(f.fsPath) === '.sql');
|
||||
should(project.files.filter(f => f.type === EntryType.File).length).equal(expectedScripts.length, 'Unexpected number of scripts in project');
|
||||
|
||||
const expectedFolders = fileList.filter(f => path.extname(f.fsPath) === '' && f.fsPath.toUpperCase() !== newProjFolder.toUpperCase());
|
||||
should(project.files.filter(f => f.type === EntryType.Folder).length).equal(expectedFolders.length, 'Unexpected number of folders in project');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function setupDeleteExcludeTest(proj: Project): Promise<[FileProjectEntry, ProjectRootTreeItem, FileProjectEntry, FileProjectEntry, FileProjectEntry]> {
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function createTestDataSources(contents: string, folderPath?: strin
|
||||
}
|
||||
|
||||
export async function generateTestFolderPath(): Promise<string> {
|
||||
const folderPath = path.join(os.tmpdir(), `TestRun_${new Date().getTime()}`);
|
||||
const folderPath = path.join(os.tmpdir(), 'ADS_Tests', `TestRun_${new Date().getTime()}`);
|
||||
await fs.mkdir(folderPath, { recursive: true });
|
||||
|
||||
return folderPath;
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as should from 'should';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { createDummyFileStructure } from './testUtils';
|
||||
import { exists, trimUri, removeSqlCmdVariableFormatting, formatSqlCmdVariable, isValidSqlCmdVariableName, timeConversion, validateSqlServerPortNumber, isEmptyString } from '../common/utils';
|
||||
import { exists, trimUri, removeSqlCmdVariableFormatting, formatSqlCmdVariable, isValidSqlCmdVariableName, timeConversion, validateSqlServerPortNumber, isEmptyString, detectCommandInstallation } from '../common/utils';
|
||||
import { Uri } from 'vscode';
|
||||
|
||||
describe('Tests to verify utils functions', function (): void {
|
||||
@@ -105,5 +105,10 @@ describe('Tests to verify utils functions', function (): void {
|
||||
should(isEmptyString(undefined)).equals(true);
|
||||
should(isEmptyString('65536')).equals(false);
|
||||
});
|
||||
|
||||
it('Should correctly detect present commands', async () => {
|
||||
should(await detectCommandInstallation('node')).equal(true, '"node" should have been detected.');
|
||||
should(await detectCommandInstallation('bogusFakeCommand')).equal(false, '"bogusFakeCommand" should have been detected.');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user