Fix script generated for SQL Assessment results (#1058)

* INSERT VALUES has the limit of 1000 rows. Replace with derived table.
* Remove NOT NULL restriction from the generated table.
* Fix line endings in SQL Assessment source files.
This commit is contained in:
Aleksei Guzev
2020-08-28 20:17:54 +03:00
committed by GitHub
parent 0b905fef75
commit 07700560a6
8 changed files with 1222 additions and 1212 deletions

View File

@@ -1,129 +1,129 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Management.Assessment;
namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts
{ {
/// <summary> /// <summary>
/// Parameters for executing a query from a provided string /// Parameters for executing a query from a provided string
/// </summary> /// </summary>
public class AssessmentParams public class AssessmentParams
{ {
/// <summary> /// <summary>
/// Gets or sets the owner uri to get connection from /// Gets or sets the owner uri to get connection from
/// </summary> /// </summary>
public string OwnerUri { get; set; } public string OwnerUri { get; set; }
/// <summary> /// <summary>
/// Gets or sets the target type /// Gets or sets the target type
/// </summary> /// </summary>
public SqlObjectType TargetType { get; set; } public SqlObjectType TargetType { get; set; }
} }
/// <summary> /// <summary>
/// Describes an item returned by SQL Assessment RPC methods /// Describes an item returned by SQL Assessment RPC methods
/// </summary> /// </summary>
public class AssessmentItemInfo public class AssessmentItemInfo
{ {
/// <summary> /// <summary>
/// Gets or sets assessment ruleset version. /// Gets or sets assessment ruleset version.
/// </summary> /// </summary>
public string RulesetVersion { get; set; } public string RulesetVersion { get; set; }
/// <summary> /// <summary>
/// Gets or sets assessment ruleset name /// Gets or sets assessment ruleset name
/// </summary> /// </summary>
public string RulesetName { get; set; } public string RulesetName { get; set; }
/// <summary> /// <summary>
/// Gets or sets assessed target's type. /// Gets or sets assessed target's type.
/// Supported values: 1 - server, 2 - database. /// Supported values: 1 - server, 2 - database.
/// </summary> /// </summary>
public SqlObjectType TargetType { get; set; } public SqlObjectType TargetType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the assessed object's name. /// Gets or sets the assessed object's name.
/// </summary> /// </summary>
public string TargetName { get; set; } public string TargetName { get; set; }
/// <summary> /// <summary>
/// Gets or sets check's ID. /// Gets or sets check's ID.
/// </summary> /// </summary>
public string CheckId { get; set; } public string CheckId { get; set; }
/// <summary> /// <summary>
/// Gets or sets tags assigned to this item. /// Gets or sets tags assigned to this item.
/// </summary> /// </summary>
public string[] Tags { get; set; } public string[] Tags { get; set; }
/// <summary> /// <summary>
/// Gets or sets a display name for this item. /// Gets or sets a display name for this item.
/// </summary> /// </summary>
public string DisplayName { get; set; } public string DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets a brief description of the item's purpose. /// Gets or sets a brief description of the item's purpose.
/// </summary> /// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets a <see cref="string"/> containing /// Gets or sets a <see cref="string"/> containing
/// an link to a page providing detailed explanation /// an link to a page providing detailed explanation
/// of the best practice. /// of the best practice.
/// </summary> /// </summary>
public string HelpLink { get; set; } public string HelpLink { get; set; }
/// <summary> /// <summary>
/// Gets or sets a <see cref="string"/> indicating /// Gets or sets a <see cref="string"/> indicating
/// severity level assigned to this items. /// severity level assigned to this items.
/// Values are: "Information", "Warning", "Critical". /// Values are: "Information", "Warning", "Critical".
/// </summary> /// </summary>
public string Level { get; set; } public string Level { get; set; }
} }
/// <summary> /// <summary>
/// Generic SQL Assessment Result /// Generic SQL Assessment Result
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// Result item's type derived from <see cref="AssessmentItemInfo"/> /// Result item's type derived from <see cref="AssessmentItemInfo"/>
/// </typeparam> /// </typeparam>
public class AssessmentResultData<T> public class AssessmentResultData<T>
where T : AssessmentItemInfo where T : AssessmentItemInfo
{ {
/// <summary> /// <summary>
/// Gets the collection of assessment results. /// Gets the collection of assessment results.
/// </summary> /// </summary>
public List<T> Items { get; } = new List<T>(); public List<T> Items { get; } = new List<T>();
/// <summary> /// <summary>
/// Gets or sets SQL Assessment API version. /// Gets or sets SQL Assessment API version.
/// </summary> /// </summary>
public string ApiVersion { get; set; } public string ApiVersion { get; set; }
} }
/// <summary> /// <summary>
/// Generic SQL Assessment Result /// Generic SQL Assessment Result
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// Result item's type derived from <see cref="AssessmentItemInfo"/> /// Result item's type derived from <see cref="AssessmentItemInfo"/>
/// </typeparam> /// </typeparam>
public class AssessmentResult<T> : AssessmentResultData<T> public class AssessmentResult<T> : AssessmentResultData<T>
where T : AssessmentItemInfo where T : AssessmentItemInfo
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating /// Gets or sets a value indicating
/// if assessment operation was successful. /// if assessment operation was successful.
/// </summary> /// </summary>
public bool Success { get; set; } public bool Success { get; set; }
/// <summary> /// <summary>
/// Gets or sets an status message for the operation. /// Gets or sets an status message for the operation.
/// </summary> /// </summary>
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
} }
} }

View File

@@ -1,56 +1,56 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts
{ {
/// <summary> /// <summary>
/// Parameters for executing a query from a provided string /// Parameters for executing a query from a provided string
/// </summary> /// </summary>
public class GenerateScriptParams public class GenerateScriptParams
{ {
/// <summary> /// <summary>
/// Gets or sets a list of assessment result items /// Gets or sets a list of assessment result items
/// to be written to a table /// to be written to a table
/// </summary> /// </summary>
public List<AssessmentResultItem> Items { get; set; } public List<AssessmentResultItem> Items { get; set; }
public TaskExecutionMode TaskExecutionMode { get; set; } public TaskExecutionMode TaskExecutionMode { get; set; }
public string TargetServerName { get; set; } public string TargetServerName { get; set; }
public string TargetDatabaseName { get; set; } public string TargetDatabaseName { get; set; }
} }
public class GenerateScriptResult public class GenerateScriptResult
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating /// Gets or sets a value indicating
/// if assessment operation was successful /// if assessment operation was successful
/// </summary> /// </summary>
public bool Success { get; set; } public bool Success { get; set; }
/// <summary> /// <summary>
/// Gets or sets an status message for the operation /// Gets or sets an status message for the operation
/// </summary> /// </summary>
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
/// <summary> /// <summary>
/// Gets or sets script text /// Gets or sets script text
/// </summary> /// </summary>
public string Script { get; set; } public string Script { get; set; }
} }
public class GenerateScriptRequest public class GenerateScriptRequest
{ {
public static readonly public static readonly
RequestType<GenerateScriptParams, ResultStatus> Type = RequestType<GenerateScriptParams, ResultStatus> Type =
RequestType<GenerateScriptParams, ResultStatus>.Create("assessment/generateScript"); RequestType<GenerateScriptParams, ResultStatus>.Create("assessment/generateScript");
} }
} }

View File

@@ -1,33 +1,33 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts
{ {
/// <summary> /// <summary>
/// Parameters for executing a query from a provided string /// Parameters for executing a query from a provided string
/// </summary> /// </summary>
public class GetAssessmentItemsParams : AssessmentParams public class GetAssessmentItemsParams : AssessmentParams
{ {
// a placeholder for future specialization // a placeholder for future specialization
} }
/// <summary> /// <summary>
/// Describes a check used to assess SQL Server objects. /// Describes a check used to assess SQL Server objects.
/// </summary> /// </summary>
public class CheckInfo : AssessmentItemInfo public class CheckInfo : AssessmentItemInfo
{ {
// a placeholder for future specialization // a placeholder for future specialization
} }
public class GetAssessmentItemsRequest public class GetAssessmentItemsRequest
{ {
public static readonly RequestType<GetAssessmentItemsParams, AssessmentResult<CheckInfo>> Type = public static readonly RequestType<GetAssessmentItemsParams, AssessmentResult<CheckInfo>> Type =
RequestType<GetAssessmentItemsParams, AssessmentResult<CheckInfo>>.Create( RequestType<GetAssessmentItemsParams, AssessmentResult<CheckInfo>>.Create(
"assessment/getAssessmentItems"); "assessment/getAssessmentItems");
} }
} }

View File

@@ -1,82 +1,82 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System; using System;
using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts
{ {
/// <summary> /// <summary>
/// Parameters for executing a query from a provided string /// Parameters for executing a query from a provided string
/// </summary> /// </summary>
public class InvokeParams : AssessmentParams public class InvokeParams : AssessmentParams
{ {
// a placeholder for future specialization // a placeholder for future specialization
} }
/// <summary> /// <summary>
/// SQL Assessment result item kind. /// SQL Assessment result item kind.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// SQL Assessment run is a set of checks. Every check /// SQL Assessment run is a set of checks. Every check
/// may return a result item. Normally it is a note containing /// may return a result item. Normally it is a note containing
/// recommendations on improving target's configuration. /// recommendations on improving target's configuration.
/// But some checks may fail to obtain data due to access /// But some checks may fail to obtain data due to access
/// restrictions or data integrity. In this case /// restrictions or data integrity. In this case
/// the check produces an error or a warning. /// the check produces an error or a warning.
/// </remarks> /// </remarks>
public enum AssessmentResultItemKind public enum AssessmentResultItemKind
{ {
/// <summary> /// <summary>
/// SQL Assessment item contains recommendation /// SQL Assessment item contains recommendation
/// </summary> /// </summary>
Note = 0, Note = 0,
/// <summary> /// <summary>
/// SQL Assessment item contains a warning on /// SQL Assessment item contains a warning on
/// limited assessment capabilities /// limited assessment capabilities
/// </summary> /// </summary>
Warning = 1, Warning = 1,
/// <summary> /// <summary>
/// SQL Assessment item contain a description of /// SQL Assessment item contain a description of
/// error occured in the course of assessment run /// error occured in the course of assessment run
/// </summary> /// </summary>
Error = 2 Error = 2
} }
/// <summary> /// <summary>
/// Describes an assessment result item /// Describes an assessment result item
/// containing a recommendation based on best practices. /// containing a recommendation based on best practices.
/// </summary> /// </summary>
public class AssessmentResultItem : AssessmentItemInfo public class AssessmentResultItem : AssessmentItemInfo
{ {
/// <summary> /// <summary>
/// Gets or sets a message to the user /// Gets or sets a message to the user
/// containing the recommendation. /// containing the recommendation.
/// </summary> /// </summary>
public string Message { get; set; } public string Message { get; set; }
/// <summary> /// <summary>
/// Gets or sets result type: /// Gets or sets result type:
/// 0 - real result, 1 - warning, 2 - error. /// 0 - real result, 1 - warning, 2 - error.
/// </summary> /// </summary>
public AssessmentResultItemKind Kind { get; set; } public AssessmentResultItemKind Kind { get; set; }
/// <summary> /// <summary>
/// Gets or sets date and time /// Gets or sets date and time
/// when the item had been acquired. /// when the item had been acquired.
/// </summary> /// </summary>
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
} }
public class InvokeRequest public class InvokeRequest
{ {
public static readonly public static readonly
RequestType<InvokeParams, AssessmentResult<AssessmentResultItem>> Type = RequestType<InvokeParams, AssessmentResult<AssessmentResultItem>> Type =
RequestType<InvokeParams, AssessmentResult<AssessmentResultItem>>.Create("assessment/invoke"); RequestType<InvokeParams, AssessmentResult<AssessmentResultItem>>.Create("assessment/invoke");
} }
} }

View File

@@ -27,8 +27,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment
/// <summary> /// <summary>
/// Gets the unique id associated with this instance. /// Gets the unique id associated with this instance.
/// </summary> /// </summary>
public string OperationId { get; set; } public string OperationId { get; set; }
/// <summary> /// <summary>
/// Gets the parameters containing assessment results /// Gets the parameters containing assessment results
/// to be stored in a data table. /// to be stored in a data table.
@@ -44,8 +44,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment
/// <summary> /// <summary>
/// Gets or sets the sql task that's executing the operation /// Gets or sets the sql task that's executing the operation
/// </summary> /// </summary>
public SqlTask SqlTask { get; set; } public SqlTask SqlTask { get; set; }
public GenerateScriptOperation(GenerateScriptParams parameters) public GenerateScriptOperation(GenerateScriptParams parameters)
{ {
Validate.IsNotNull(nameof(parameters), parameters); Validate.IsNotNull(nameof(parameters), parameters);
@@ -92,24 +92,29 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlAssessment
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
const string scriptPrologue = const string scriptPrologue =
@"IF (NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'AssessmentResult')) @"IF (NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'AssessmentResult'))
BEGIN BEGIN
CREATE TABLE [dbo].[AssessmentResult]( CREATE TABLE [dbo].[AssessmentResult](
[CheckName] [nvarchar](max) NOT NULL, [CheckName] [nvarchar](max),
[CheckId] [nvarchar](max) NOT NULL, [CheckId] [nvarchar](max),
[RulesetName] [nvarchar](max) NOT NULL, [RulesetName] [nvarchar](max),
[RulesetVersion] [nvarchar](max) NOT NULL, [RulesetVersion] [nvarchar](max),
[Severity] [nvarchar](max) NOT NULL, [Severity] [nvarchar](max),
[Message] [nvarchar](max) NOT NULL, [Message] [nvarchar](max),
[TargetPath] [nvarchar](max) NOT NULL, [TargetPath] [nvarchar](max),
[TargetType] [nvarchar](max) NOT NULL, [TargetType] [nvarchar](max),
[HelpLink] [nvarchar](max) NOT NULL, [HelpLink] [nvarchar](max),
[Timestamp] [datetimeoffset](7) NOT NULL [Timestamp] [datetimeoffset](7)
) )
END END
GO GO
INSERT INTO [dbo].[AssessmentResult] ([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp]) INSERT INTO [dbo].[AssessmentResult] ([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp])
VALUES"; SELECT rpt.[CheckName],rpt.[CheckId],rpt.[RulesetName],rpt.[RulesetVersion],rpt.[Severity],rpt.[Message],rpt.[TargetPath],rpt.[TargetType],rpt.[HelpLink],rpt.[Timestamp]
FROM (VALUES ";
const string scriptEpilogue =
@"
) rpt([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp])";
var sb = new StringBuilder(); var sb = new StringBuilder();
if (generateScriptParams.Items != null) if (generateScriptParams.Items != null)
@@ -122,11 +127,14 @@ VALUES";
if (item.Kind == AssessmentResultItemKind.Note) if (item.Kind == AssessmentResultItemKind.Note)
{ {
sb.Append( sb.Append(
$"\r\n('{CUtils.EscapeStringSQuote(item.DisplayName)}','{CUtils.EscapeStringSQuote(item.CheckId)}','{CUtils.EscapeStringSQuote(item.RulesetName)}','{item.RulesetVersion}','{item.Level}','{CUtils.EscapeStringSQuote(item.Message)}','{CUtils.EscapeStringSQuote(item.TargetName)}','{item.TargetType}','{CUtils.EscapeStringSQuote(item.HelpLink)}','{item.Timestamp:yyyy-MM-dd hh:mm:ss.fff zzz}'),"); $@"
('{CUtils.EscapeStringSQuote(item.DisplayName)}','{CUtils.EscapeStringSQuote(item.CheckId)}','{CUtils.EscapeStringSQuote(item.RulesetName)}','{item.RulesetVersion}','{item.Level}','{CUtils.EscapeStringSQuote(item.Message)}','{CUtils.EscapeStringSQuote(item.TargetName)}','{item.TargetType}','{CUtils.EscapeStringSQuote(item.HelpLink)}','{item.Timestamp:yyyy-MM-dd hh:mm:ss.fff zzz}'),");
} }
} }
sb.Length -= 1; sb.Length -= 1;
sb.Append(scriptEpilogue);
} }
return sb.ToString(); return sb.ToString();

View File

@@ -1,225 +1,225 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Management.Assessment;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
using Microsoft.SqlTools.ServiceLayer.SqlAssessment; using Microsoft.SqlTools.ServiceLayer.SqlAssessment;
using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts;
using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.Test.Common;
using NUnit.Framework; using NUnit.Framework;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlAssessment namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlAssessment
{ {
public class SqlAssessmentServiceTests public class SqlAssessmentServiceTests
{ {
private delegate Task<List<TResult>> AssessmentMethod<TResult>(SqlObjectLocator locator); private delegate Task<List<TResult>> AssessmentMethod<TResult>(SqlObjectLocator locator);
private static readonly string[] AllowedSeverityLevels = { "Information", "Warning", "Critical" }; private static readonly string[] AllowedSeverityLevels = { "Information", "Warning", "Critical" };
[Test] [Test]
public async Task InvokeSqlAssessmentServerTest() public async Task InvokeSqlAssessmentServerTest()
{ {
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master"); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master");
var connection = liveConnection.ConnectionInfo.AllConnections.FirstOrDefault(); var connection = liveConnection.ConnectionInfo.AllConnections.FirstOrDefault();
Debug.Assert(connection != null, "Live connection is always expected providing a connection"); Debug.Assert(connection != null, "Live connection is always expected providing a connection");
var serverInfo = ReliableConnectionHelper.GetServerVersion(connection); var serverInfo = ReliableConnectionHelper.GetServerVersion(connection);
var response = await CallAssessment<AssessmentResultItem>( var response = await CallAssessment<AssessmentResultItem>(
nameof(SqlAssessmentService.InvokeSqlAssessment), nameof(SqlAssessmentService.InvokeSqlAssessment),
SqlObjectType.Server, SqlObjectType.Server,
liveConnection); liveConnection);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(response.Items.Select(i => i.Message), Has.All.Not.Null.Or.Empty); Assert.That(response.Items.Select(i => i.Message), Has.All.Not.Null.Or.Empty);
Assert.That(response.Items.Select(i => i.TargetName), Has.All.EqualTo(serverInfo.ServerName)); Assert.That(response.Items.Select(i => i.TargetName), Has.All.EqualTo(serverInfo.ServerName));
foreach (var i in response.Items.Where(i => i.Kind == 0)) foreach (var i in response.Items.Where(i => i.Kind == 0))
{ {
AssertInfoPresent(i); AssertInfoPresent(i);
} }
}); });
} }
[Test] [Test]
public async Task GetAssessmentItemsServerTest() public async Task GetAssessmentItemsServerTest()
{ {
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master"); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo("master");
var connection = liveConnection.ConnectionInfo.AllConnections.FirstOrDefault(); var connection = liveConnection.ConnectionInfo.AllConnections.FirstOrDefault();
Debug.Assert(connection != null, "Live connection is always expected providing a connection"); Debug.Assert(connection != null, "Live connection is always expected providing a connection");
var serverInfo = ReliableConnectionHelper.GetServerVersion(connection); var serverInfo = ReliableConnectionHelper.GetServerVersion(connection);
var response = await CallAssessment<CheckInfo>( var response = await CallAssessment<CheckInfo>(
nameof(SqlAssessmentService.GetAssessmentItems), nameof(SqlAssessmentService.GetAssessmentItems),
SqlObjectType.Server, SqlObjectType.Server,
liveConnection); liveConnection);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(response.Items.Select(i => i.TargetName), Has.All.EqualTo(serverInfo.ServerName)); Assert.That(response.Items.Select(i => i.TargetName), Has.All.EqualTo(serverInfo.ServerName));
foreach (var i in response.Items) foreach (var i in response.Items)
{ {
AssertInfoPresent(i); AssertInfoPresent(i);
} }
}); });
} }
[Test] [Test]
public async Task GetAssessmentItemsDatabaseTest() public async Task GetAssessmentItemsDatabaseTest()
{ {
const string DatabaseName = "tempdb"; const string DatabaseName = "tempdb";
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(DatabaseName); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(DatabaseName);
var response = await CallAssessment<CheckInfo>( var response = await CallAssessment<CheckInfo>(
nameof(SqlAssessmentService.GetAssessmentItems), nameof(SqlAssessmentService.GetAssessmentItems),
SqlObjectType.Database, SqlObjectType.Database,
liveConnection); liveConnection);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(response.Items.Select(i => i.TargetName), Has.All.EndsWith(":" + DatabaseName)); Assert.That(response.Items.Select(i => i.TargetName), Has.All.EndsWith(":" + DatabaseName));
foreach (var i in response.Items) foreach (var i in response.Items)
{ {
AssertInfoPresent(i); AssertInfoPresent(i);
} }
}); });
} }
[Test] [Test]
public async Task InvokeSqlAssessmentIDatabaseTest() public async Task InvokeSqlAssessmentIDatabaseTest()
{ {
const string DatabaseName = "tempdb"; const string DatabaseName = "tempdb";
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(DatabaseName); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(DatabaseName);
var response = await CallAssessment<AssessmentResultItem>( var response = await CallAssessment<AssessmentResultItem>(
nameof(SqlAssessmentService.InvokeSqlAssessment), nameof(SqlAssessmentService.InvokeSqlAssessment),
SqlObjectType.Database, SqlObjectType.Database,
liveConnection); liveConnection);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(response.Items.Select(i => i.Message), Has.All.Not.Null.Or.Empty); Assert.That(response.Items.Select(i => i.Message), Has.All.Not.Null.Or.Empty);
Assert.That(response.Items.Select(i => i.TargetName), Has.All.EndsWith(":" + DatabaseName)); Assert.That(response.Items.Select(i => i.TargetName), Has.All.EndsWith(":" + DatabaseName));
foreach (var i in response.Items.Where(i => i.Kind == 0)) foreach (var i in response.Items.Where(i => i.Kind == 0))
{ {
AssertInfoPresent(i); AssertInfoPresent(i);
} }
}); });
} }
private static async Task<AssessmentResult<TResult>> CallAssessment<TResult>( private static async Task<AssessmentResult<TResult>> CallAssessment<TResult>(
string methodName, string methodName,
SqlObjectType sqlObjectType, SqlObjectType sqlObjectType,
LiveConnectionHelper.TestConnectionResult liveConnection) LiveConnectionHelper.TestConnectionResult liveConnection)
where TResult : AssessmentItemInfo where TResult : AssessmentItemInfo
{ {
var connInfo = liveConnection.ConnectionInfo; var connInfo = liveConnection.ConnectionInfo;
AssessmentResult<TResult> response; AssessmentResult<TResult> response;
using (var service = new SqlAssessmentService( using (var service = new SqlAssessmentService(
TestServiceProvider.Instance.ConnectionService, TestServiceProvider.Instance.ConnectionService,
TestServiceProvider.Instance.WorkspaceService)) TestServiceProvider.Instance.WorkspaceService))
{ {
AddTestRules(service); AddTestRules(service);
string randomUri = Guid.NewGuid().ToString(); string randomUri = Guid.NewGuid().ToString();
AssessmentParams requestParams = AssessmentParams requestParams =
new AssessmentParams { OwnerUri = randomUri, TargetType = sqlObjectType }; new AssessmentParams { OwnerUri = randomUri, TargetType = sqlObjectType };
ConnectParams connectParams = new ConnectParams ConnectParams connectParams = new ConnectParams
{ {
OwnerUri = requestParams.OwnerUri, OwnerUri = requestParams.OwnerUri,
Connection = connInfo.ConnectionDetails, Connection = connInfo.ConnectionDetails,
Type = ConnectionType.Default Type = ConnectionType.Default
}; };
var methodInfo = typeof(SqlAssessmentService).GetMethod( var methodInfo = typeof(SqlAssessmentService).GetMethod(
methodName, methodName,
BindingFlags.Instance | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(methodInfo); Assert.NotNull(methodInfo);
var func = (AssessmentMethod<TResult>)Delegate.CreateDelegate( var func = (AssessmentMethod<TResult>)Delegate.CreateDelegate(
typeof(AssessmentMethod<TResult>), typeof(AssessmentMethod<TResult>),
service, service,
methodInfo); methodInfo);
response = await service.CallAssessmentEngine<TResult>( response = await service.CallAssessmentEngine<TResult>(
requestParams, requestParams,
connectParams, connectParams,
randomUri, randomUri,
t => func(t)); t => func(t));
} }
Assert.NotNull(response); Assert.NotNull(response);
if (response.Success) if (response.Success)
{ {
Assert.That(response.Items.Select(i => i.TargetType), Has.All.EqualTo(sqlObjectType)); Assert.That(response.Items.Select(i => i.TargetType), Has.All.EqualTo(sqlObjectType));
Assert.That(response.Items.Select(i => i.Level), Has.All.AnyOf(AllowedSeverityLevels)); Assert.That(response.Items.Select(i => i.Level), Has.All.AnyOf(AllowedSeverityLevels));
} }
return response; return response;
} }
private static void AddTestRules(SqlAssessmentService service) private static void AddTestRules(SqlAssessmentService service)
{ {
const string TestRuleset = @" const string TestRuleset = @"
{ {
'name': 'Tags & Checks', 'name': 'Tags & Checks',
'version': '0.3', 'version': '0.3',
'schemaVersion': '1.0', 'schemaVersion': '1.0',
'rules': [ 'rules': [
{ {
'id': 'ServerRule', 'id': 'ServerRule',
'itemType': 'definition', 'itemType': 'definition',
'tags': [ 'Test' ], 'tags': [ 'Test' ],
'displayName': 'Test server check', 'displayName': 'Test server check',
'description': 'This check always fails for testing purposes.', 'description': 'This check always fails for testing purposes.',
'message': 'This check intentionally fails', 'message': 'This check intentionally fails',
'target': { 'type': 'Server' } 'target': { 'type': 'Server' }
}, },
{ {
'id': 'DatabaseRule', 'id': 'DatabaseRule',
'itemType': 'definition', 'itemType': 'definition',
'tags': [ 'Test' ], 'tags': [ 'Test' ],
'displayName': 'Test server check', 'displayName': 'Test server check',
'description': 'This check always fails for testing purposes.', 'description': 'This check always fails for testing purposes.',
'message': 'This check intentionally fails', 'message': 'This check intentionally fails',
'target': { 'type': 'Database' } 'target': { 'type': 'Database' }
} }
] ]
} }
"; ";
using (var reader = new StringReader(TestRuleset)) using (var reader = new StringReader(TestRuleset))
{ {
service.Engine.PushRuleFactoryJson(reader); service.Engine.PushRuleFactoryJson(reader);
} }
} }
private void AssertInfoPresent(AssessmentItemInfo item) private void AssertInfoPresent(AssessmentItemInfo item)
{ {
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(item.CheckId, Is.Not.Null.Or.Empty); Assert.That(item.CheckId, Is.Not.Null.Or.Empty);
Assert.That(item.DisplayName, Is.Not.Null.Or.Empty); Assert.That(item.DisplayName, Is.Not.Null.Or.Empty);
Assert.That(item.Description, Is.Not.Null.Or.Empty); Assert.That(item.Description, Is.Not.Null.Or.Empty);
Assert.NotNull(item.Tags); Assert.NotNull(item.Tags);
Assert.That(item.Tags, Has.All.Not.Null.Or.Empty); Assert.That(item.Tags, Has.All.Not.Null.Or.Empty);
}); });
} }
} }
} }

View File

@@ -1,157 +1,159 @@
// //
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Assessment; using Microsoft.SqlServer.Management.Assessment;
using Microsoft.SqlTools.ServiceLayer.SqlAssessment; using Microsoft.SqlTools.ServiceLayer.SqlAssessment;
using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlAssessment.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlAssessment namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlAssessment
{ {
public class GenerateScriptOperationTests public class GenerateScriptOperationTests
{ {
private static readonly GenerateScriptParams SampleParams = new GenerateScriptParams private static readonly GenerateScriptParams SampleParams = new GenerateScriptParams
{ {
Items = new List<AssessmentResultItem> Items = new List<AssessmentResultItem>
{ {
new AssessmentResultItem new AssessmentResultItem
{ {
CheckId = "C1", CheckId = "C1",
Description = "Desc1", Description = "Desc1",
DisplayName = "DN1", DisplayName = "DN1",
HelpLink = "HL1", HelpLink = "HL1",
Kind = AssessmentResultItemKind.Note, Kind = AssessmentResultItemKind.Note,
Level = "Information", Level = "Information",
Message = "Msg'1", Message = "Msg'1",
TargetName = "proj[*]_dev", TargetName = "proj[*]_dev",
TargetType = SqlObjectType.Server, TargetType = SqlObjectType.Server,
Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.Zero), Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.Zero),
RulesetName = "Microsoft Ruleset", RulesetName = "Microsoft Ruleset",
RulesetVersion = "1.3" RulesetVersion = "1.3"
}, },
new AssessmentResultItem new AssessmentResultItem
{ {
CheckId = "C-2", CheckId = "C-2",
Description = "Desc2", Description = "Desc2",
DisplayName = "D N2", DisplayName = "D N2",
HelpLink = "http://HL2", HelpLink = "http://HL2",
Kind = AssessmentResultItemKind.Warning, Kind = AssessmentResultItemKind.Warning,
Level = "Warning", Level = "Warning",
Message = "Msg'1", Message = "Msg'1",
TargetName = "proj[*]_devW", TargetName = "proj[*]_devW",
TargetType = SqlObjectType.Database, TargetType = SqlObjectType.Database,
Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromHours(3)), Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromHours(3)),
RulesetName = "Microsoft Ruleset", RulesetName = "Microsoft Ruleset",
RulesetVersion = "1.3" RulesetVersion = "1.3"
}, },
new AssessmentResultItem new AssessmentResultItem
{ {
CheckId = "C'3", CheckId = "C'3",
Description = "Des'c3", Description = "Des'c3",
DisplayName = "D'N1", DisplayName = "D'N1",
HelpLink = "HL'1", HelpLink = "HL'1",
Kind = AssessmentResultItemKind.Error, Kind = AssessmentResultItemKind.Error,
Level = "Critical", Level = "Critical",
Message = "Msg'1", Message = "Msg'1",
TargetName = "proj[*]_devE", TargetName = "proj[*]_devE",
TargetType = SqlObjectType.Server, TargetType = SqlObjectType.Server,
Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromMinutes(-90)), Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromMinutes(-90)),
RulesetName = "Microsoft Ruleset", RulesetName = "Microsoft Ruleset",
RulesetVersion = "1.3" RulesetVersion = "1.3"
}, },
new AssessmentResultItem new AssessmentResultItem
{ {
CheckId = "C-2", CheckId = "C-2",
Description = "Desc2", Description = "Desc2",
DisplayName = "D N2", DisplayName = "D N2",
HelpLink = "http://HL2", HelpLink = "http://HL2",
Kind = AssessmentResultItemKind.Note, Kind = AssessmentResultItemKind.Note,
Level = "Warning", Level = "Warning",
Message = "Msg'1", Message = "Msg'1",
TargetName = "proj[*]_dev", TargetName = "proj[*]_dev",
TargetType = SqlObjectType.Database, TargetType = SqlObjectType.Database,
Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromHours(3)), Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromHours(3)),
RulesetName = "Microsoft Ruleset", RulesetName = "Microsoft Ruleset",
RulesetVersion = "1.3" RulesetVersion = "1.3"
}, },
new AssessmentResultItem new AssessmentResultItem
{ {
CheckId = "C'3", CheckId = "C'3",
Description = "Des'c3", Description = "Des'c3",
DisplayName = "D'N1", DisplayName = "D'N1",
HelpLink = "HL'1", HelpLink = "HL'1",
Kind = AssessmentResultItemKind.Note, Kind = AssessmentResultItemKind.Note,
Level = "Critical", Level = "Critical",
Message = "Msg'1", Message = "Msg'1",
TargetName = "proj[*]_dev", TargetName = "proj[*]_dev",
TargetType = SqlObjectType.Server, TargetType = SqlObjectType.Server,
Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromMinutes(-90)), Timestamp = new DateTimeOffset(2001, 5, 25, 13, 42, 00, TimeSpan.FromMinutes(-90)),
RulesetName = "Microsoft Ruleset", RulesetName = "Microsoft Ruleset",
RulesetVersion = "1.3" RulesetVersion = "1.3"
} }
} }
}; };
private const string SampleScript = private const string SampleScript =
@"IF (NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'AssessmentResult')) @"IF (NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'AssessmentResult'))
BEGIN BEGIN
CREATE TABLE [dbo].[AssessmentResult]( CREATE TABLE [dbo].[AssessmentResult](
[CheckName] [nvarchar](max) NOT NULL, [CheckName] [nvarchar](max),
[CheckId] [nvarchar](max) NOT NULL, [CheckId] [nvarchar](max),
[RulesetName] [nvarchar](max) NOT NULL, [RulesetName] [nvarchar](max),
[RulesetVersion] [nvarchar](max) NOT NULL, [RulesetVersion] [nvarchar](max),
[Severity] [nvarchar](max) NOT NULL, [Severity] [nvarchar](max),
[Message] [nvarchar](max) NOT NULL, [Message] [nvarchar](max),
[TargetPath] [nvarchar](max) NOT NULL, [TargetPath] [nvarchar](max),
[TargetType] [nvarchar](max) NOT NULL, [TargetType] [nvarchar](max),
[HelpLink] [nvarchar](max) NOT NULL, [HelpLink] [nvarchar](max),
[Timestamp] [datetimeoffset](7) NOT NULL [Timestamp] [datetimeoffset](7)
) )
END END
GO GO
INSERT INTO [dbo].[AssessmentResult] ([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp]) INSERT INTO [dbo].[AssessmentResult] ([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp])
VALUES SELECT rpt.[CheckName],rpt.[CheckId],rpt.[RulesetName],rpt.[RulesetVersion],rpt.[Severity],rpt.[Message],rpt.[TargetPath],rpt.[TargetType],rpt.[HelpLink],rpt.[Timestamp]
('DN1','C1','Microsoft Ruleset','1.3','Information','Msg''1','proj[*]_dev','Server','HL1','2001-05-25 01:42:00.000 +00:00'), FROM (VALUES
('D N2','C-2','Microsoft Ruleset','1.3','Warning','Msg''1','proj[*]_dev','Database','http://HL2','2001-05-25 01:42:00.000 +03:00'), ('DN1','C1','Microsoft Ruleset','1.3','Information','Msg''1','proj[*]_dev','Server','HL1','2001-05-25 01:42:00.000 +00:00'),
('D''N1','C''3','Microsoft Ruleset','1.3','Critical','Msg''1','proj[*]_dev','Server','HL''1','2001-05-25 01:42:00.000 -01:30')"; ('D N2','C-2','Microsoft Ruleset','1.3','Warning','Msg''1','proj[*]_dev','Database','http://HL2','2001-05-25 01:42:00.000 +03:00'),
('D''N1','C''3','Microsoft Ruleset','1.3','Critical','Msg''1','proj[*]_dev','Server','HL''1','2001-05-25 01:42:00.000 -01:30')
[Test] ) rpt([CheckName],[CheckId],[RulesetName],[RulesetVersion],[Severity],[Message],[TargetPath],[TargetType],[HelpLink],[Timestamp])";
public void GenerateScriptTest()
{ [Test]
var scriptText = GenerateScriptOperation.GenerateScript(SampleParams, CancellationToken.None); public void GenerateScriptTest()
Assert.AreEqual(SampleScript, scriptText); {
} var scriptText = GenerateScriptOperation.GenerateScript(SampleParams, CancellationToken.None);
Assert.AreEqual(SampleScript, scriptText);
[Test] }
public void ExecuteTest()
{ [Test]
var subject = new GenerateScriptOperation(SampleParams); public void ExecuteTest()
var taskMetadata = new TaskMetadata(); {
using (var sqlTask = new SqlTask(taskMetadata, DummyOpFunction, DummyOpFunction)) var subject = new GenerateScriptOperation(SampleParams);
{ var taskMetadata = new TaskMetadata();
subject.SqlTask = sqlTask; using (var sqlTask = new SqlTask(taskMetadata, DummyOpFunction, DummyOpFunction))
sqlTask.ScriptAdded += ValidateScriptAdded; {
subject.Execute(TaskExecutionMode.Script); subject.SqlTask = sqlTask;
} sqlTask.ScriptAdded += ValidateScriptAdded;
} subject.Execute(TaskExecutionMode.Script);
}
private void ValidateScriptAdded(object sender, TaskEventArgs<TaskScript> e) }
{
Assert.AreEqual(SqlTaskStatus.Succeeded, e.TaskData.Status); private void ValidateScriptAdded(object sender, TaskEventArgs<TaskScript> e)
Assert.AreEqual(SampleScript, e.TaskData.Script); {
} Assert.AreEqual(SqlTaskStatus.Succeeded, e.TaskData.Status);
Assert.AreEqual(SampleScript, e.TaskData.Script);
private static Task<TaskResult> DummyOpFunction(SqlTask _) }
{
return Task.FromResult(new TaskResult() {TaskStatus = SqlTaskStatus.Succeeded}); private static Task<TaskResult> DummyOpFunction(SqlTask _)
} {
} return Task.FromResult(new TaskResult() {TaskStatus = SqlTaskStatus.Succeeded});
} }
}
}