mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-20 01:25:37 -05:00
Import project from database (#10326)
* Initial changes for Import database as new project * Functionally complete code * Initial changes for Import database as new project * Functionally complete code * Resolved conflicts with latest changes. Also did some code refactoring. * Addressed comments. Added unit tests. * Addressed comments * Moved ExtractTarget enum from azdata to mssql * Addressed comments * Fixed indentation in project templates
This commit is contained in:
@@ -55,10 +55,10 @@
|
||||
<Import Condition="'$(NetCoreBuild)' == 'true'" Project="$(NETCoreTargetsPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets"/>
|
||||
<Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Condition="'$(NetCoreBuild)' == 'true'" Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -52,8 +52,12 @@
|
||||
<SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
|
||||
<VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<Import Condition="'$(NetCoreBuild)' == 'true'" Project="$(NETCoreTargetsPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets"/>
|
||||
<Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Condition="'$(NetCoreBuild)' == 'true'" Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties" />
|
||||
<Folder Include="Tables" />
|
||||
|
||||
@@ -54,4 +54,17 @@ describe('Project: sqlproj content operations', function (): void {
|
||||
|
||||
should (newFileContents).equal(fileContents);
|
||||
});
|
||||
|
||||
it('Should add Folder and Build entries to sqlproj with pre-existing scripts on disk', async function (): Promise<void> {
|
||||
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
||||
const project: Project = new Project(projFilePath);
|
||||
await project.readProjFile();
|
||||
|
||||
let list: string[] = await testUtils.createListOfFiles(path.dirname(projFilePath));
|
||||
|
||||
await project.addToProject(list);
|
||||
|
||||
should(project.files.filter(f => f.type === EntryType.File).length).equal(11); // txt file shouldn't be added to the project
|
||||
should(project.files.filter(f => f.type === EntryType.Folder).length).equal(3); // 2folders + default Properties folder
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
import * as should from 'should';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as baselines from './baselines/baselines';
|
||||
import * as templates from '../templates/templates';
|
||||
import * as testUtils from './testUtils';
|
||||
import * as constants from '../common/constants';
|
||||
|
||||
import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider';
|
||||
import { ProjectsController } from '../controllers/projectController';
|
||||
@@ -23,6 +25,23 @@ import { IDeploymentProfile, IGenerateScriptProfile } from '../models/IDeploymen
|
||||
|
||||
let testContext: TestContext;
|
||||
|
||||
// Mock test data
|
||||
const mockConnectionProfile: azdata.IConnectionProfile = {
|
||||
connectionName: 'My Connection',
|
||||
serverName: 'My Server',
|
||||
databaseName: 'My Database',
|
||||
userName: 'My User',
|
||||
password: 'My Pwd',
|
||||
authenticationType: 'SqlLogin',
|
||||
savePassword: false,
|
||||
groupFullName: 'My groupName',
|
||||
groupId: 'My GroupId',
|
||||
providerName: 'My Server',
|
||||
saveProfile: true,
|
||||
id: 'My Id',
|
||||
options: undefined as any
|
||||
};
|
||||
|
||||
describe('ProjectsController: project controller operations', function (): void {
|
||||
before(async function (): Promise<void> {
|
||||
testContext = createContext();
|
||||
@@ -126,3 +145,64 @@ describe('ProjectsController: project controller operations', function (): void
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProjectsController: import operations', function (): void {
|
||||
it('Should create list of all files and folders correctly', async function (): Promise<void> {
|
||||
const testFolderPath = await testUtils.createDummyFileStructure();
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
const fileList = await projController.generateList(testFolderPath);
|
||||
|
||||
should(fileList.length).equal(15); // Parent folder + 2 files under parent folder + 2 directories with 5 files each
|
||||
});
|
||||
|
||||
it('Should error out for inaccessible path', async function (): Promise<void> {
|
||||
let testFolderPath = await testUtils.generateTestFolderPath();
|
||||
testFolderPath += '_nonExistant'; // Modify folder path to point to a non-existant location
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
|
||||
await testUtils.shouldThrowSpecificError(async () => await projController.generateList(testFolderPath), constants.cannotResolvePath(testFolderPath));
|
||||
});
|
||||
|
||||
it('Should show error when no project name provided', async function (): Promise<void> {
|
||||
for (const name of ['', ' ', undefined]) {
|
||||
testContext.apiWrapper.reset();
|
||||
testContext.apiWrapper.setup(x => x.showInputBox(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(name));
|
||||
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
await testUtils.shouldThrowSpecificError(async () => await projController.importNewDatabaseProject(mockConnectionProfile), constants.projectNameRequired, `case: '${name}'`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should show error when no target information provided', async function (): Promise<void> {
|
||||
testContext.apiWrapper.setup(x => x.showInputBox(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('MyProjectName'));
|
||||
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
|
||||
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
await testUtils.shouldThrowSpecificError(async () => await projController.importNewDatabaseProject(mockConnectionProfile), constants.extractTargetRequired);
|
||||
});
|
||||
|
||||
it('Should show error when no location provided with ExtractTarget = File', async function (): Promise<void> {
|
||||
testContext.apiWrapper.setup(x => x.showInputBox(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('MyProjectName'));
|
||||
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({label: 'File'}));
|
||||
testContext.apiWrapper.setup(x => x.showSaveDialog(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
|
||||
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
await testUtils.shouldThrowSpecificError(async () => await projController.importNewDatabaseProject(mockConnectionProfile), constants.projectLocationRequired);
|
||||
});
|
||||
|
||||
it('Should show error when no location provided with ExtractTarget = SchemaObjectType', async function (): Promise<void> {
|
||||
testContext.apiWrapper.setup(x => x.showInputBox(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('MyProjectName'));
|
||||
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny())).returns(() => Promise.resolve({label: 'SchemaObjectType'}));
|
||||
testContext.apiWrapper.setup(x => x.showOpenDialog(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
|
||||
testContext.apiWrapper.setup(x => x.workspaceFolders()).returns(() => undefined);
|
||||
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
|
||||
|
||||
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
|
||||
await testUtils.shouldThrowSpecificError(async () => await projController.importNewDatabaseProject(mockConnectionProfile), constants.projectLocationRequired);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,3 +55,65 @@ async function createTestFile(contents: string, fileName: string, folderPath?: s
|
||||
|
||||
return filePath;
|
||||
}
|
||||
/**
|
||||
* TestFolder directory structure
|
||||
* - file1.sql
|
||||
* - folder1
|
||||
* -file1.sql
|
||||
* -file2.sql
|
||||
* -file3.sql
|
||||
* -file4.sql
|
||||
* -file5.sql
|
||||
* - folder2
|
||||
* -file1.sql
|
||||
* -file2.sql
|
||||
* -file3.sql
|
||||
* -file4.sql
|
||||
* -file5.sql
|
||||
* - file2.txt
|
||||
*
|
||||
* @param createList Boolean specifying to create a list of the files and folders been created
|
||||
* @param list List of files and folders that are been created
|
||||
*/
|
||||
export async function createDummyFileStructure(createList?: boolean, list?: string[], testFolderPath?: string): Promise<string> {
|
||||
testFolderPath = testFolderPath ?? await generateTestFolderPath();
|
||||
|
||||
let filePath = path.join(testFolderPath, 'file1.sql');
|
||||
await fs.open(filePath, 'w');
|
||||
if (createList) {
|
||||
list?.push(testFolderPath);
|
||||
list?.push(filePath);
|
||||
}
|
||||
|
||||
for (let dirCount = 1; dirCount <= 2; dirCount++) {
|
||||
let dirName = path.join(testFolderPath, `folder${dirCount}`);
|
||||
await fs.mkdir(dirName, { recursive: true });
|
||||
if (createList) {
|
||||
list?.push(dirName);
|
||||
}
|
||||
|
||||
for (let fileCount = 1; fileCount <= 5; fileCount++) {
|
||||
let fileName = path.join(dirName, `file${fileCount}.sql`);
|
||||
await fs.open(fileName, 'w');
|
||||
if (createList) {
|
||||
list?.push(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filePath = path.join(testFolderPath, 'file2.txt');
|
||||
await fs.open(filePath, 'w');
|
||||
if (createList) {
|
||||
list?.push(filePath);
|
||||
}
|
||||
|
||||
return testFolderPath;
|
||||
}
|
||||
|
||||
export async function createListOfFiles(filePath?: string): Promise<string[]> {
|
||||
let fileFolderList: string[] = [];
|
||||
|
||||
await createDummyFileStructure(true, fileFolderList, filePath);
|
||||
|
||||
return fileFolderList;
|
||||
}
|
||||
|
||||
30
extensions/sql-database-projects/src/test/utils.test.ts
Normal file
30
extensions/sql-database-projects/src/test/utils.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 path from 'path';
|
||||
import {createDummyFileStructure} from './testUtils';
|
||||
import {toPascalCase, exists} from '../common/utils';
|
||||
|
||||
describe('Tests for conversion within PascalCase and camelCase', function (): void {
|
||||
it('Should generate PascalCase from camelCase correctly', async () => {
|
||||
should(toPascalCase('')).equal('');
|
||||
should(toPascalCase('camelCase')).equal('CamelCase');
|
||||
should(toPascalCase('camel.case')).equal('Camel.case');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tests to verify exists function', function (): void {
|
||||
it('Should determine existance of files/folders', async () => {
|
||||
let testFolderPath = await createDummyFileStructure();
|
||||
|
||||
should(await exists(testFolderPath)).equal(true);
|
||||
should(await exists(path.join(testFolderPath, 'file1.sql'))).equal(true);
|
||||
should(await exists(path.join(testFolderPath, 'folder2'))).equal(true);
|
||||
should(await exists(path.join(testFolderPath, 'folder4'))).equal(false);
|
||||
should(await exists(path.join(testFolderPath, 'folder2','file4.sql'))).equal(true);
|
||||
should(await exists(path.join(testFolderPath, 'folder4','file2.sql'))).equal(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user