diff --git a/extensions/sql-database-projects/images/sqlEdgeProject.svg b/extensions/sql-database-projects/images/sqlEdgeProject.svg new file mode 100644 index 0000000000..19bc948f03 --- /dev/null +++ b/extensions/sql-database-projects/images/sqlEdgeProject.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-database-projects/resources/templates/newTsqlDataSourceTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlDataSourceTemplate.sql new file mode 100644 index 0000000000..b40c679cea --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlDataSourceTemplate.sql @@ -0,0 +1,4 @@ +CREATE EXTERNAL DATA SOURCE [@@OBJECT_NAME@@] WITH +( + LOCATION = '@@LOCATION@@' +) diff --git a/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamTemplate.sql new file mode 100644 index 0000000000..94f0d2a011 --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamTemplate.sql @@ -0,0 +1,5 @@ +CREATE EXTERNAL STREAM [@@OBJECT_NAME@@] WITH +( + DATA_SOURCE = [@@DATA_SOURCE_NAME@@], + LOCATION = N'@@LOCATION@@'@@OPTIONS@@ +) diff --git a/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamingJobTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamingJobTemplate.sql index 015ea4ae67..476425ad7b 100644 --- a/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamingJobTemplate.sql +++ b/extensions/sql-database-projects/resources/templates/newTsqlExternalStreamingJobTemplate.sql @@ -5,6 +5,5 @@ EXEC sys.sp_create_streaming_job @NAME = '@@OBJECT_NAME@@', @STATEMENT = 'INSERT INTO SqlOutputStream SELECT timeCreated, - streamColumn1 as column1, - streamColumn2 as column2 + streamColumn1 as Id FROM EdgeHubInputStream' diff --git a/extensions/sql-database-projects/resources/templates/newTsqlFileFormatTemplate.sql b/extensions/sql-database-projects/resources/templates/newTsqlFileFormatTemplate.sql new file mode 100644 index 0000000000..c1418c4af1 --- /dev/null +++ b/extensions/sql-database-projects/resources/templates/newTsqlFileFormatTemplate.sql @@ -0,0 +1,5 @@ +CREATE EXTERNAL FILE FORMAT [@@OBJECT_NAME@@] WITH +( + FORMAT_TYPE = JSON, + DATA_COMPRESSION = 'org.apache.hadoop.io.compress.GzipCodec' +) diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index abfc0f82d5..832b6111dd 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -23,9 +23,13 @@ export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.S export const databaseSchemaProvider = 'DatabaseSchemaProvider'; // Project Provider -export const sqlDatabaseProjectTypeId = 'sqldbproj'; -export const projectTypeDisplayName = localize('projectTypeDisplayName', "SQL Database"); -export const projectTypeDescription = localize('projectTypeDescription', "Design, edit, and publish schemas for SQL databases"); +export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj'; +export const emptyProjectTypeDisplayName = localize('emptyProjectTypeDisplayName', "SQL Database"); +export const emptyProjectTypeDescription = localize('emptyProjectTypeDescription', "Develop and publish schemas for SQL databases starting from an empty project"); + +export const edgeSqlDatabaseProjectTypeId = 'SqlDbEdgeProj'; +export const edgeProjectTypeDisplayName = localize('edgeProjectTypeDisplayName', "SQL Edge"); +export const edgeProjectTypeDescription = localize('edgeProjectTypeDescription', "Start with the core pieces to develop and publish schemas for SQL Edge"); // commands export const revealFileInOsCommand = 'revealFileInOS'; @@ -196,6 +200,9 @@ export const scriptFriendlyName = localize('scriptFriendlyName', "Script"); export const tableFriendlyName = localize('tableFriendlyName', "Table"); export const viewFriendlyName = localize('viewFriendlyName', "View"); export const storedProcedureFriendlyName = localize('storedProcedureFriendlyName', "Stored Procedure"); +export const dataSourceFriendlyName = localize('dataSource', "Data Source"); +export const fileFormatFriendlyName = localize('fileFormat', "File Format"); +export const externalStreamFriendlyName = localize('externalStream', "External Stream"); export const externalStreamingJobFriendlyName = localize('externalStreamingJobFriendlyName', "External Streaming Job"); export const preDeployScriptFriendlyName = localize('preDeployScriptFriendlyName', "Script.PreDeployment"); export const postDeployScriptFriendlyName = localize('postDeployScriptFriendlyName', "Script.PostDeployment"); diff --git a/extensions/sql-database-projects/src/common/iconHelper.ts b/extensions/sql-database-projects/src/common/iconHelper.ts index e048477661..5467b1f2d4 100644 --- a/extensions/sql-database-projects/src/common/iconHelper.ts +++ b/extensions/sql-database-projects/src/common/iconHelper.ts @@ -14,6 +14,7 @@ export class IconPathHelper { private static extensionContext: vscode.ExtensionContext; public static databaseProject: IconPath; public static colorfulSqlProject: IconPath; + public static sqlEdgeProject: IconPath; public static dataSourceGroup: IconPath; public static dataSourceSql: IconPath; @@ -33,6 +34,7 @@ export class IconPathHelper { IconPathHelper.databaseProject = IconPathHelper.makeIcon('databaseProject'); IconPathHelper.colorfulSqlProject = IconPathHelper.makeIcon('colorfulSqlProject', true); + IconPathHelper.sqlEdgeProject = IconPathHelper.makeIcon('sqlEdgeProject', true); IconPathHelper.dataSourceGroup = IconPathHelper.makeIcon('dataSourceGroup'); IconPathHelper.dataSourceSql = IconPathHelper.makeIcon('dataSource-sql'); diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index 9982cd79e2..8d8c324acf 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -56,40 +56,35 @@ export class ProjectsController { * @param folderUri * @param projectGuid */ - public async createNewProject(newProjName: string, folderUri: vscode.Uri, makeOwnFolder: boolean, projectGuid?: string): Promise { - if (projectGuid && !UUID.isUUID(projectGuid)) { - throw new Error(`Specified GUID is invalid: '${projectGuid}'`); + public async createNewProject(creationParams: NewProjectParams): Promise { + if (creationParams.projectGuid && !UUID.isUUID(creationParams.projectGuid)) { + throw new Error(`Specified GUID is invalid: '${creationParams.projectGuid}'`); } const macroDict: Record = { - 'PROJECT_NAME': newProjName, - 'PROJECT_GUID': projectGuid ?? UUID.generateUuid().toUpperCase() + 'PROJECT_NAME': creationParams.newProjName, + 'PROJECT_GUID': creationParams.projectGuid ?? UUID.generateUuid().toUpperCase() }; - let newProjFileContents = this.macroExpansion(templates.newSqlProjectTemplate, macroDict); + let newProjFileContents = templates.macroExpansion(templates.newSqlProjectTemplate, macroDict); - let newProjFileName = newProjName; + let newProjFileName = creationParams.newProjName; if (!newProjFileName.toLowerCase().endsWith(constants.sqlprojExtension)) { newProjFileName += constants.sqlprojExtension; } - const newProjFilePath = makeOwnFolder ? path.join(folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName) : path.join(folderUri.fsPath, newProjFileName); + const newProjFilePath = path.join(creationParams.folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName); - let fileExists = false; - try { - await fs.access(newProjFilePath); - fileExists = true; - } - catch { } // file doesn't already exist - - if (fileExists) { + if (await utils.exists(newProjFilePath)) { throw new Error(constants.projectAlreadyExists(newProjFileName, path.parse(newProjFilePath).dir)); } await fs.mkdir(path.dirname(newProjFilePath), { recursive: true }); await fs.writeFile(newProjFilePath, newProjFileContents); + await this.addTemplateFiles(newProjFilePath, creationParams.projectTypeId); + return newProjFilePath; } @@ -256,7 +251,7 @@ export class ProjectsController { } } - const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()]; + const itemType = templates.get(itemTypeName); const absolutePathToParent = path.join(project.projectFolderPath, relativePath); let itemObjectName = await this.promptForNewObjectName(itemType, project, absolutePathToParent, constants.sqlFileExtension); @@ -266,18 +261,10 @@ export class ProjectsController { return; // user cancelled } - const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName }); + const newFileText = templates.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName }); const relativeFilePath = path.join(relativePath, itemObjectName + constants.sqlFileExtension); try { - // check if file already exists - const absoluteFilePath = path.join(project.projectFolderPath, relativeFilePath); - const fileExists = await utils.exists(absoluteFilePath); - - if (fileExists) { - throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name)); - } - const newEntry = await project.addScriptItem(relativeFilePath, newFileText, itemType.type); await vscode.commands.executeCommand(constants.vscodeOpenCommand, newEntry.fsUri); @@ -460,6 +447,12 @@ export class ProjectsController { return addDatabaseReferenceDialog; } + /** + * Adds a database reference to a project, after selections have been made in the dialog + * @param project project to which to add the database reference + * @param settings settings for the database reference + * @param context a treeItem in a project's hierarchy, to be used to obtain a Project + */ public async addDatabaseReferenceCallback(project: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings, context: dataworkspace.WorkspaceTreeItem): Promise { try { if ((settings).projectName !== undefined) { @@ -494,6 +487,11 @@ export class ProjectsController { } } + /** + * Validates the contents of an external streaming job's query against the last-built dacpac. + * If no dacpac exists at the output path, one will be built first. + * @param node a treeItem in a project's hierarchy, to be used to obtain a Project + */ public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise { const project: Project = this.getProjectFromContext(node); @@ -547,6 +545,29 @@ export class ProjectsController { } } + private async addTemplateFiles(newProjFilePath: string, projectTypeId: string): Promise { + if (projectTypeId === constants.emptySqlDatabaseProjectTypeId || newProjFilePath === '') { + return; + } + + if (projectTypeId === constants.edgeSqlDatabaseProjectTypeId) { + const project = await Project.openProject(newProjFilePath); + + await this.createFileFromTemplate(project, templates.get(templates.table), 'DataTable.sql', { 'OBJECT_NAME': 'DataTable' }); + await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'EdgeHubInputDataSource.sql', { 'OBJECT_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'edgehub://' }); + await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'SqlOutputDataSource.sql', { 'OBJECT_NAME': 'SqlOutputDataSource', 'LOCATION': 'sqlserver://tcp:.,1433' }); + await this.createFileFromTemplate(project, templates.get(templates.fileFormat), 'StreamFileFormat.sql', { 'OBJECT_NAME': 'StreamFileFormat' }); + await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'EdgeHubInputStream.sql', { 'OBJECT_NAME': 'EdgeHubInputStream', 'DATA_SOURCE_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'input', 'OPTIONS': ',\n\tFILE_FORMAT = StreamFileFormat' }); + await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'SqlOutputStream.sql', { 'OBJECT_NAME': 'SqlOutputStream', 'DATA_SOURCE_NAME': 'SqlOutputDataSource', 'LOCATION': 'TSQLStreaming.dbo.DataTable', 'OPTIONS': '' }); + await this.createFileFromTemplate(project, templates.get(templates.externalStreamingJob), 'EdgeStreamingJob.sql', { 'OBJECT_NAME': 'EdgeStreamingJob' }); + } + } + + private async createFileFromTemplate(project: Project, itemType: templates.ProjectScriptType, relativePath: string, expansionMacros: Record): Promise { + const newFileText = templates.macroExpansion(itemType.templateScript, expansionMacros); + await project.addScriptItem(relativePath, newFileText, itemType.type); + } + private getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Project { if ('element' in context) { return context.element.root.project; @@ -570,21 +591,7 @@ export class ProjectsController { return (ext.exports as mssql.IExtension).dacFx; } - private macroExpansion(template: string, macroDict: Record): string { - const macroIndicator = '@@'; - let output = template; - for (const macro in macroDict) { - // check if value contains the macroIndicator, which could break expansion for successive macros - if (macroDict[macro].includes(macroIndicator)) { - throw new Error(`Macro value ${macroDict[macro]} is invalid because it contains ${macroIndicator}`); - } - - output = output.replace(new RegExp(macroIndicator + macro + macroIndicator, 'g'), macroDict[macro]); - } - - return output; - } private async promptForNewObjectName(itemType: templates.ProjectScriptType, _project: Project, folderPath: string, fileExtension?: string): Promise { const suggestedName = itemType.friendlyName.replace(/\s+/g, ''); @@ -632,7 +639,12 @@ export class ProjectsController { const newProjFolderUri = model.filePath; - const newProjFilePath = await this.createNewProject(model.projName, vscode.Uri.file(newProjFolderUri), true); + const newProjFilePath = await this.createNewProject({ + newProjName: model.projName, + folderUri: vscode.Uri.file(newProjFolderUri), + projectTypeId: constants.emptySqlDatabaseProjectTypeId + }); + model.filePath = path.dirname(newProjFilePath); this.setFilePath(model); @@ -718,3 +730,10 @@ export class ProjectsController { //#endregion } + +export interface NewProjectParams { + newProjName: string; + folderUri: vscode.Uri; + projectTypeId: string; + projectGuid?: string; +} diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index a5718c8130..47354ad741 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -282,19 +282,26 @@ export class Project { * @param contents Contents to be written to the new file */ public async addScriptItem(relativeFilePath: string, contents?: string, itemType?: string): Promise { + // check if file already exists const absoluteFilePath = path.join(this.projectFolderPath, relativeFilePath); + if (contents !== undefined && contents !== '' && await utils.exists(absoluteFilePath)) { + throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name)); + } + + // create the file if (contents) { await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true }); await fs.writeFile(absoluteFilePath, contents); } - //Check that file actually exists + // check that file was successfully created let exists = await utils.exists(absoluteFilePath); if (!exists) { throw new Error(constants.noFileExist(absoluteFilePath)); } + // update sqlproj XML const fileEntry = this.createFileProjectEntry(relativeFilePath, EntryType.File); let xmlTag; diff --git a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts index 0b753d0f66..54883ef580 100644 --- a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts +++ b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts @@ -5,7 +5,7 @@ import * as dataworkspace from 'dataworkspace'; import * as vscode from 'vscode'; -import { sqlprojExtension, projectTypeDisplayName, projectTypeDescription, sqlDatabaseProjectTypeId } from '../common/constants'; +import * as constants from '../common/constants'; import { IconPathHelper } from '../common/iconHelper'; import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider'; import { ProjectsController } from '../controllers/projectController'; @@ -44,11 +44,18 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide */ get supportedProjectTypes(): dataworkspace.IProjectType[] { return [{ - id: sqlDatabaseProjectTypeId, - projectFileExtension: sqlprojExtension.replace(/\./g, ''), - displayName: projectTypeDisplayName, - description: projectTypeDescription, + id: constants.emptySqlDatabaseProjectTypeId, + projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''), + displayName: constants.emptyProjectTypeDisplayName, + description: constants.emptyProjectTypeDescription, icon: IconPathHelper.colorfulSqlProject + }, + { + id: constants.edgeSqlDatabaseProjectTypeId, + projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''), + displayName: constants.edgeProjectTypeDisplayName, + description: constants.edgeProjectTypeDescription, + icon: IconPathHelper.sqlEdgeProject }]; } @@ -56,10 +63,16 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide * Create a project * @param name name of the project * @param location the parent directory + * @param projectTypeId the ID of the project/template * @returns Uri of the newly created project file */ - async createProject(name: string, location: vscode.Uri, _: string): Promise { - const projectFile = await this.projectController.createNewProject(name, location, true); + async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise { + const projectFile = await this.projectController.createNewProject({ + newProjName: name, + folderUri: location, + projectTypeId: projectTypeId + }); + return vscode.Uri.file(projectFile); } } diff --git a/extensions/sql-database-projects/src/templates/templates.ts b/extensions/sql-database-projects/src/templates/templates.ts index 2427083bbd..d83068d488 100644 --- a/extensions/sql-database-projects/src/templates/templates.ts +++ b/extensions/sql-database-projects/src/templates/templates.ts @@ -15,6 +15,9 @@ export const script: string = 'script'; export const table: string = 'table'; export const view: string = 'view'; export const storedProcedure: string = 'storedProcedure'; +export const dataSource: string = 'dataSource'; +export const fileFormat: string = 'fileFormat'; +export const externalStream: string = 'externalStream'; export const externalStreamingJob: string = 'externalStreamingJob'; export const folder: string = 'folder'; @@ -25,12 +28,12 @@ export const postDeployScript: string = 'postDeployScript'; let scriptTypeMap: Record = {}; -export function projectScriptTypeMap(): Record { +export function get(key: string): ProjectScriptType { if (Object.keys(scriptTypeMap).length === 0) { throw new Error('Templates must be loaded from file before attempting to use.'); } - return scriptTypeMap; + return scriptTypeMap[key.toLocaleLowerCase()]; } let scriptTypes: ProjectScriptType[] = []; @@ -44,6 +47,8 @@ export function projectScriptTypes(): ProjectScriptType[] { } export async function loadTemplates(templateFolderPath: string) { + reset(); + await Promise.all([ Promise.resolve(newSqlProjectTemplate = await loadTemplate(templateFolderPath, 'newSqlProjectTemplate.xml')), loadObjectTypeInfo(script, constants.scriptFriendlyName, templateFolderPath, 'newTsqlScriptTemplate.sql'), @@ -52,11 +57,14 @@ export async function loadTemplates(templateFolderPath: string) { loadObjectTypeInfo(storedProcedure, constants.storedProcedureFriendlyName, templateFolderPath, 'newTsqlStoredProcedureTemplate.sql'), loadObjectTypeInfo(preDeployScript, constants.preDeployScriptFriendlyName, templateFolderPath, 'newTsqlPreDeployScriptTemplate.sql'), loadObjectTypeInfo(postDeployScript, constants.postDeployScriptFriendlyName, templateFolderPath, 'newTsqlPostDeployScriptTemplate.sql'), + loadObjectTypeInfo(dataSource, constants.dataSourceFriendlyName, templateFolderPath, 'newTsqlDataSourceTemplate.sql'), + loadObjectTypeInfo(fileFormat, constants.fileFormatFriendlyName, templateFolderPath, 'newTsqlFileFormatTemplate.sql'), + loadObjectTypeInfo(externalStream, constants.externalStreamFriendlyName, templateFolderPath, 'newTsqlExternalStreamTemplate.sql'), loadObjectTypeInfo(externalStreamingJob, constants.externalStreamingJobFriendlyName, templateFolderPath, 'newTsqlExternalStreamingJobTemplate.sql') ]); for (const scriptType of scriptTypes) { - if (Object.keys(projectScriptTypeMap).find(s => s === scriptType.type.toLocaleLowerCase() || s === scriptType.friendlyName.toLocaleLowerCase())) { + if (Object.keys(scriptTypeMap).find(s => s === scriptType.type.toLocaleLowerCase() || s === scriptType.friendlyName.toLocaleLowerCase())) { throw new Error(`Script type map already contains ${scriptType.type} or its friendlyName.`); } @@ -65,9 +73,27 @@ export async function loadTemplates(templateFolderPath: string) { } } -async function loadObjectTypeInfo(key: string, friendlyName: string, templateFolderPath: string, fileName: string) { +export function macroExpansion(template: string, macroDict: Record): string { + const macroIndicator = '@@'; + let output = template; + + for (const macro in macroDict) { + // check if value contains the macroIndicator, which could break expansion for successive macros + if (macroDict[macro].includes(macroIndicator)) { + throw new Error(`Macro value ${macroDict[macro]} is invalid because it contains ${macroIndicator}`); + } + + output = output.replace(new RegExp(macroIndicator + macro + macroIndicator, 'g'), macroDict[macro]); + } + + return output; +} + +async function loadObjectTypeInfo(key: string, friendlyName: string, templateFolderPath: string, fileName: string): Promise { const template = await loadTemplate(templateFolderPath, fileName); scriptTypes.push(new ProjectScriptType(key, friendlyName, template)); + + return key; } async function loadTemplate(templateFolderPath: string, fileName: string): Promise { diff --git a/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts b/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts index fa24f04a77..92539b62eb 100644 --- a/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts +++ b/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts @@ -16,6 +16,7 @@ import { PublishDatabaseDialog } from '../../dialogs/publishDatabaseDialog'; import { Project } from '../../models/project'; import { ProjectsController } from '../../controllers/projectController'; import { IPublishSettings, IGenerateScriptSettings } from '../../models/IPublishSettings'; +import { emptySqlDatabaseProjectTypeId } from '../../common/constants'; describe.skip('Publish Database Dialog', () => { before(async function (): Promise { @@ -27,7 +28,13 @@ describe.skip('Publish Database Dialog', () => { const projController = new ProjectsController(); const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`); - const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), true, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'); + const projFilePath = await projController.createNewProject({ + newProjName: 'TestProjectName', + folderUri: vscode.Uri.file(projFileDir), + projectTypeId: emptySqlDatabaseProjectTypeId, + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + }); + const project = new Project(projFilePath); const publishDatabaseDialog = new PublishDatabaseDialog(project); publishDatabaseDialog.openDialog(); @@ -39,7 +46,13 @@ describe.skip('Publish Database Dialog', () => { const projFolder = `TestProject_${new Date().getTime()}`; const projFileDir = path.join(os.tmpdir(), projFolder); - const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), true, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'); + const projFilePath = await projController.createNewProject({ + newProjName: 'TestProjectName', + folderUri: vscode.Uri.file(projFileDir), + projectTypeId: emptySqlDatabaseProjectTypeId, + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + }); + const project = new Project(projFilePath); const publishDatabaseDialog = new PublishDatabaseDialog(project); @@ -55,7 +68,7 @@ describe.skip('Publish Database Dialog', () => { let profile: IPublishSettings | IGenerateScriptSettings | undefined; - const expectedPublish: IPublishSettings = { + const expectedPublish: IPublishSettings = { databaseName: 'MockDatabaseName', connectionUri: 'Mock|Connection|Uri', upgradeExisting: true, diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 787458b62f..f04ded635c 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -54,7 +54,12 @@ describe('ProjectsController', function (): void { const projController = new ProjectsController(); const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`); - const projFilePath = await projController.createNewProject('TestProjectName', vscode.Uri.file(projFileDir), false, 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575'); + const projFilePath = await projController.createNewProject({ + newProjName: 'TestProjectName', + folderUri: vscode.Uri.file(projFileDir), + projectTypeId: constants.emptySqlDatabaseProjectTypeId, + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + }); let projFileText = (await fs.readFile(projFilePath)).toString(); @@ -152,6 +157,7 @@ describe('ProjectsController', function (): void { it('Should delete nested ProjectEntry from node', async function (): Promise { let proj = await testUtils.createTestProject(templates.newSqlProjectTemplate); + const setupResult = await setupDeleteExcludeTest(proj); const scriptEntry = setupResult[0], projTreeRoot = setupResult[1], preDeployEntry = setupResult[2], postDeployEntry = setupResult[3], noneEntry = setupResult[4]; @@ -436,7 +442,15 @@ describe('ProjectsController', function (): void { const createProjectFromDatabaseDialog = TypeMoq.Mock.ofType(CreateProjectFromDatabaseDialog, undefined, undefined, undefined); createProjectFromDatabaseDialog.callBase = true; createProjectFromDatabaseDialog.setup(x => x.handleCreateButtonClick()).returns(async () => { - await projController.object.createProjectFromDatabaseCallback( { serverId: 'My Id', database: 'My Database', projName: 'testProject', filePath: 'testLocation', version: '1.0.0.0', extractTarget: mssql.ExtractTarget['schemaObjectType'] }); + await projController.object.createProjectFromDatabaseCallback({ + serverId: 'My Id', + database: 'My Database', + projName: 'testProject', + filePath: 'testLocation', + version: '1.0.0.0', + extractTarget: mssql.ExtractTarget['schemaObjectType'] + }); + return Promise.resolve(undefined); }); diff --git a/extensions/sql-database-projects/src/test/templates.test.ts b/extensions/sql-database-projects/src/test/templates.test.ts index 9557d87aff..71f505647b 100644 --- a/extensions/sql-database-projects/src/test/templates.test.ts +++ b/extensions/sql-database-projects/src/test/templates.test.ts @@ -14,8 +14,8 @@ describe('Templates: loading templates from disk', function (): void { }); it('Should throw error when attempting to use templates before loaded from file', async function (): Promise { - await shouldThrowSpecificError(() => templates.projectScriptTypeMap(), 'Templates must be loaded from file before attempting to use.'); - await shouldThrowSpecificError(() => templates.projectScriptTypes(), 'Templates must be loaded from file before attempting to use.'); + await shouldThrowSpecificError(() => templates.get('foobar'), 'Templates must be loaded from file before attempting to use.'); + await shouldThrowSpecificError(() => templates.get('foobar'), 'Templates must be loaded from file before attempting to use.'); }); it('Should load all templates from files', async function (): Promise { @@ -23,7 +23,7 @@ describe('Templates: loading templates from disk', function (): void { // check expected counts - const numScriptObjectTypes = 7; + const numScriptObjectTypes = 10; should(templates.projectScriptTypes().length).equal(numScriptObjectTypes); should(Object.keys(templates.projectScriptTypes()).length).equal(numScriptObjectTypes); diff --git a/extensions/sql-database-projects/src/test/testUtils.ts b/extensions/sql-database-projects/src/test/testUtils.ts index bdc0f5d348..31cc943191 100644 --- a/extensions/sql-database-projects/src/test/testUtils.ts +++ b/extensions/sql-database-projects/src/test/testUtils.ts @@ -28,6 +28,7 @@ export async function shouldThrowSpecificError(block: Function, expectedMessage: } export async function createTestSqlProjFile(contents: string, folderPath?: string): Promise { + folderPath = folderPath ?? path.join(await generateTestFolderPath(), 'TestProject'); return await createTestFile(contents, 'TestProject.sqlproj', folderPath); } @@ -40,7 +41,7 @@ export async function createTestDataSources(contents: string, folderPath?: strin } export async function generateTestFolderPath(): Promise { - const folderPath = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`); + const folderPath = path.join(os.tmpdir(), `TestRun_${new Date().getTime()}`); await fs.mkdir(folderPath, { recursive: true }); return folderPath; @@ -55,6 +56,7 @@ export async function createTestFile(contents: string, fileName: string, folderP return filePath; } + /** * TestFolder directory structure * - file1.sql