diff --git a/.github/instructions/SqlServerDsc-guidelines.instructions.md b/.github/instructions/SqlServerDsc-guidelines.instructions.md index c03e1d867c..f72e14e524 100644 --- a/.github/instructions/SqlServerDsc-guidelines.instructions.md +++ b/.github/instructions/SqlServerDsc-guidelines.instructions.md @@ -27,7 +27,6 @@ applyTo: "**" ## SQL Server Interaction - Always prefer SMO over T-SQL - Unit tests: Use SMO stub types from SMO.cs, never mock SMO types -- Run tests in new session after changing SMO.cs ## Testing CI Environment - Database Engine: instance `DSCSQLTEST` diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index 7695a1f7b3..08ef42c190 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -52,10 +52,11 @@ applyTo: "**/*.[Tt]ests.ps1" - Public commands: `tests/Unit/Public/{Name}.Tests.ps1` - Private functions: `tests/Unit/Private/{Name}.Tests.ps1` -## Data-Driven Tests -- Define variables in separate `BeforeDiscovery` for `-ForEach` (close to usage) +## Data-Driven Tests (Test Cases) +- Define `-ForEach` variables in `BeforeDiscovery` (close to usage) - `-ForEach` allowed on `Context` and `It` blocks -- Keep scope close to usage context +- Never add `param()` inside Pester blocks when using `-ForEach` +- Access test case properties directly: `$PropertyName` ## Best Practices - Cover all scenarios and code paths diff --git a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md index 6ee0ca7dc3..4360eab6f8 100644 --- a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md @@ -188,7 +188,7 @@ function Get-Something - Parameter type on line above parameter name - Parameters separated by blank line - Parameters should use full type name. -- Pipeline parameters (`ValueFromPipeline = $true`) must be declared in ALL parameter sets +- `ValueFromPipeline` must be consistent across all parameter sets declarations for the same parameter ## Best Practices diff --git a/.vscode/settings.json b/.vscode/settings.json index b871104ba3..14bbda0996 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -91,7 +91,23 @@ "SOURCEBRANCHNAME", "setvariable", "RAISERROR", - "filegroup" + "filegroup", + "checkdb", + "msdb", + "db_datawriter", + "db_ddladmin", + "db_owner", + "db_accessadmin", + "db_securityadmin", + "db_backupoperator", + "db_denydatareader", + "db_denydatawriter", + "OLTP", + "LCID", + "varchar", + "maxdop", + "hotfixes", + "checkpointing" ], "cSpell.ignorePaths": [ ".git" diff --git a/CHANGELOG.md b/CHANGELOG.md index d903537f94..8c91360dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added integration tests for `Test-SqlDscIsDatabasePrincipal` command to ensure it functions correctly in real environments [issue #2231](https://github.com/dsccommunity/SqlServerDsc/issues/2231). +- Added public command `Test-SqlDscDatabaseProperty` to test database properties + on SQL Server Database Engine instances. This command supports two parameter sets: + `ServerObject` with **DatabaseName**, and `DatabaseObject` (from `Get-SqlDscDatabase`). + It allows users to specify any non-collection properties of the SMO Database object + as dynamic parameters to test, returning `$true` if all tested properties match + their expected values, and `$false` otherwise. This command improves maintainability + of SQL DSC resources and provides granular database configuration testing + [issue #2306](https://github.com/dsccommunity/SqlServerDsc/issues/2306). ### Fixed diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e99327f399..9a668d328f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -337,6 +337,7 @@ stages: 'tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscDatabase.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscDatabaseProperty.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscDatabasePermission.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabasePermission.Integration.Tests.ps1' 'tests/Integration/Commands/ConvertTo-SqlDscDatabasePermission.Integration.Tests.ps1' diff --git a/source/Public/Install-SqlDscBIReportServer.ps1 b/source/Public/Install-SqlDscBIReportServer.ps1 index 50b1de5705..e40aed8a40 100644 --- a/source/Public/Install-SqlDscBIReportServer.ps1 +++ b/source/Public/Install-SqlDscBIReportServer.ps1 @@ -6,7 +6,8 @@ Installs SQL Server Power BI Report Server using the provided setup executable. .PARAMETER AcceptLicensingTerms - Required parameter to be able to run unattended install. By specifying this + Specifies that the acceptance of all license terms and notices for the + specified features is required to be able to run unattended install. By specifying this parameter you acknowledge the acceptance of all license terms and notices for the specified features, the terms and notices that the setup executable normally asks for. @@ -20,7 +21,7 @@ This parameter is mutually exclusive with the parameter Edition. .PARAMETER EditionUpgrade - Upgrades the edition of the installed product. Requires that either the + Specifies whether to upgrade the edition of the installed product. Requires that either the ProductKey or the Edition parameter is also assigned. By default no edition upgrade is performed. @@ -40,7 +41,7 @@ PI Report Server: %ProgramFiles%\Microsoft Power BI Report Server .PARAMETER SuppressRestart - Suppresses the restart of the computer after the installation is finished. + Specifies whether to suppress the restart of the computer after the installation is finished. By default the computer is restarted after the installation is finished. .PARAMETER Timeout @@ -49,11 +50,11 @@ this time, an exception will be thrown. .PARAMETER Force - If specified the command will not ask for confirmation. Same as if Confirm:$false + Specifies whether the command will not ask for confirmation. Same as if Confirm:$false is used. .PARAMETER PassThru - If specified the command will return the setup process exit code. + Specifies whether the command will return the setup process exit code. .OUTPUTS When PassThru is specified the function will return the setup process exit diff --git a/source/Public/Invoke-SqlDscQuery.ps1 b/source/Public/Invoke-SqlDscQuery.ps1 index abc7b57686..5786670b8a 100644 --- a/source/Public/Invoke-SqlDscQuery.ps1 +++ b/source/Public/Invoke-SqlDscQuery.ps1 @@ -9,7 +9,7 @@ Specifies current server connection object. .PARAMETER ServerName - Specifies the server name where the instance exist. + Specifies the server name where the instance exists. .PARAMETER InstanceName Specifies the instance name on which to execute the T-SQL query. @@ -29,21 +29,21 @@ Specifies the name of the database to execute the T-SQL query in. .PARAMETER Query - The query string to execute. + Specifies the query string to execute. .PARAMETER PassThru - Specifies if the command should return any result the query might return. + Specifies whether the command should return any result the query might return. .PARAMETER StatementTimeout - Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). + Specifies the query StatementTimeout in seconds. Default 600 seconds (10 minutes). .PARAMETER RedactText - One or more text strings to redact from the query when verbose messages + Specifies one or more text strings to redact from the query when verbose messages are written to the console. Strings will be escaped so they will not be interpreted as regular expressions (RegEx). .PARAMETER Encrypt - Specifies if encryption should be used. + Specifies whether encryption should be used. .PARAMETER Force Specifies that the query should be executed without any confirmation. diff --git a/source/Public/New-SqlDscAgentOperator.ps1 b/source/Public/New-SqlDscAgentOperator.ps1 index ec48e8f8a9..cd13e569b7 100644 --- a/source/Public/New-SqlDscAgentOperator.ps1 +++ b/source/Public/New-SqlDscAgentOperator.ps1 @@ -45,7 +45,7 @@ Specifies the weekday pager start time for the SQL Agent Operator. .PARAMETER PassThru - If specified, the created operator object will be returned. + Specifies whether the created operator object will be returned. .PARAMETER Force Specifies that the operator should be created without any confirmation. diff --git a/source/Public/Set-SqlDscAgentAlert.ps1 b/source/Public/Set-SqlDscAgentAlert.ps1 index 8f4ea8638c..48e76bc64a 100644 --- a/source/Public/Set-SqlDscAgentAlert.ps1 +++ b/source/Public/Set-SqlDscAgentAlert.ps1 @@ -24,7 +24,7 @@ Cannot be used together with Severity. .PARAMETER PassThru - If specified, the updated alert object will be returned. + Specifies whether the updated alert object will be returned. .PARAMETER Refresh Specifies that the alert object should be refreshed before updating. This diff --git a/source/Public/Test-SqlDscDatabaseProperty.ps1 b/source/Public/Test-SqlDscDatabaseProperty.ps1 new file mode 100644 index 0000000000..e7a91140ac --- /dev/null +++ b/source/Public/Test-SqlDscDatabaseProperty.ps1 @@ -0,0 +1,1183 @@ +<# + .SYNOPSIS + Tests if database properties on a SQL Server Database Engine instance are + in the desired state. + + .DESCRIPTION + This command tests if database properties on a SQL Server Database Engine + instance are in the desired state. + + The command supports a comprehensive set of database properties including + configuration settings, metadata, security properties, performance settings, + and state information. Users can test one or multiple properties in a + single command execution. + + All properties correspond directly to Microsoft SQL Server Management Objects (SMO) + Database class properties and support the same data types and values as the + underlying SMO implementation. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the database to test properties for. The logical + database name as it appears in SQL Server. + + .PARAMETER DatabaseObject + Specifies the database object to test properties for (from Get-SqlDscDatabase). + + .PARAMETER Collation + Specifies the default collation for the database. + + .PARAMETER RecoveryModel + Specifies the database recovery model (FULL, BULK_LOGGED, SIMPLE). + + .PARAMETER CompatibilityLevel + Specifies the database compatibility level (affects query processor behavior and features). + + .PARAMETER Owner + Specifies the owner (login) of the database. + + .PARAMETER ReadOnly + Specifies whether the database is in read-only mode. + + .PARAMETER Trustworthy + Specifies whether implicit access to external resources by modules is allowed (use with caution). + + .PARAMETER AcceleratedRecoveryEnabled + Specifies whether Accelerated Database Recovery (ADR) is enabled for the database. + + .PARAMETER ActiveConnections + Specifies the number of active connections to the database (as observed by SMO). + + .PARAMETER ActiveDirectory + Specifies whether the database participates in Active Directory integration features. + + .PARAMETER AnsiNullDefault + Specifies whether new columns allow NULL by default unless explicitly specified (when ON). + + .PARAMETER AnsiNullsEnabled + Specifies whether comparisons to NULL follow ANSI SQL behavior (when ON, x = NULL yields UNKNOWN). + + .PARAMETER AnsiPaddingEnabled + Specifies whether padding for variable-length columns (e.g., CHAR/VARCHAR) follows ANSI rules. + + .PARAMETER AnsiWarningsEnabled + Specifies whether ANSI warnings are generated for certain conditions (when ON, e.g., divide by zero). + + .PARAMETER ArithmeticAbortEnabled + Specifies whether a query is terminated when an overflow or divide-by-zero error occurs. + + .PARAMETER AutoClose + Specifies whether the database closes after the last user exits. + + .PARAMETER AutoCreateIncrementalStatisticsEnabled + Specifies whether creation of incremental statistics on partitioned tables is allowed. + + .PARAMETER AutoCreateStatisticsEnabled + Specifies whether single-column statistics are automatically created for query optimization. + + .PARAMETER AutoShrink + Specifies whether the database automatically shrinks files when free space is detected. + + .PARAMETER AutoUpdateStatisticsAsync + Specifies whether statistics are updated asynchronously, allowing queries to proceed with old stats. + + .PARAMETER AutoUpdateStatisticsEnabled + Specifies whether statistics are automatically updated when they are out-of-date. + + .PARAMETER AvailabilityDatabaseSynchronizationState + Specifies the synchronization state of the database in an Availability Group. + + .PARAMETER AvailabilityGroupName + Specifies the name of the Availability Group to which the database belongs, if any. + + .PARAMETER AzureEdition + Specifies the Azure SQL Database edition (e.g., Basic/Standard/Premium/GeneralPurpose/BusinessCritical). + + .PARAMETER AzureServiceObjective + Specifies the Azure SQL Database service objective (e.g., S3, P1, GP_Gen5_4). + + .PARAMETER BrokerEnabled + Specifies whether Service Broker is enabled for the database. + + .PARAMETER CaseSensitive + Specifies whether the database collation is case-sensitive. + + .PARAMETER CatalogCollation + Specifies the catalog-level collation used for metadata and temporary objects. + + .PARAMETER ChangeTrackingAutoCleanUp + Specifies whether automatic cleanup of change tracking information is enabled. + + .PARAMETER ChangeTrackingEnabled + Specifies whether change tracking is enabled for the database. + + .PARAMETER ChangeTrackingRetentionPeriod + Specifies the retention period value for change tracking information. + + .PARAMETER ChangeTrackingRetentionPeriodUnits + Specifies the units for the retention period (e.g., DAYS, HOURS). + + .PARAMETER CloseCursorsOnCommitEnabled + Specifies whether open cursors are closed when a transaction is committed. + + .PARAMETER ConcatenateNullYieldsNull + Specifies whether concatenation with NULL results in NULL (when ON). + + .PARAMETER ContainmentType + Specifies the containment level of the database (NONE or PARTIAL). + + .PARAMETER CreateDate + Specifies the date and time that the database was created. + + .PARAMETER DatabaseEngineEdition + Specifies the edition of the database engine hosting the database. + + .PARAMETER DatabaseEngineType + Specifies the engine type (e.g., Standalone, AzureSqlDatabase, SqlOnDemand). + + .PARAMETER DatabaseGuid + Specifies the unique identifier (GUID) of the database. + + .PARAMETER DatabaseOwnershipChaining + Specifies whether ownership chaining across objects within the database is enabled. + + .PARAMETER DataRetentionEnabled + Specifies whether SQL Server data retention policy is enabled at the database level. + + .PARAMETER DateCorrelationOptimization + Specifies whether date correlation optimization is enabled to speed up temporal joins. + + .PARAMETER DboLogin + Specifies the login that owns the database (dbo). + + .PARAMETER DefaultFileGroup + Specifies the name of the default filegroup for the database. + + .PARAMETER DefaultFileStreamFileGroup + Specifies the name of the default FILESTREAM filegroup. + + .PARAMETER DefaultFullTextCatalog + Specifies the default full-text catalog used for full-text indexes. + + .PARAMETER DefaultFullTextLanguage + Specifies the LCID of the default full-text language. + + .PARAMETER DefaultLanguage + Specifies the ID of the default language for the database. + + .PARAMETER DefaultSchema + Specifies the default schema name for users without an explicit default schema. + + .PARAMETER DelayedDurability + Specifies whether delayed transaction log flushes are enabled to improve throughput. + + .PARAMETER EncryptionEnabled + Specifies whether Transparent Data Encryption (TDE) is enabled. + + .PARAMETER FilestreamDirectoryName + Specifies the directory name used for FILESTREAM data. + + .PARAMETER FilestreamNonTransactedAccess + Specifies the FILESTREAM access level for non-transactional access. + + .PARAMETER HasDatabaseEncryptionKey + Specifies whether the database has a database encryption key (TDE). + + .PARAMETER HasFileInCloud + Specifies whether the database has one or more files in Azure Storage. + + .PARAMETER HasMemoryOptimizedObjects + Specifies whether the database contains memory-optimized (In-Memory OLTP) objects. + + .PARAMETER HonorBrokerPriority + Specifies whether honoring Service Broker conversation priority is enabled. + + .PARAMETER ID + Specifies the database ID (DB_ID). Unique numeric identifier assigned to the database by SQL Server. + + .PARAMETER IndexSpaceUsage + Specifies the space used by indexes in KB. + + .PARAMETER IsAccessible + Specifies whether the database is accessible to the current connection. + + .PARAMETER IsDatabaseSnapshot + Specifies whether the database is a database snapshot. + + .PARAMETER IsDatabaseSnapshotBase + Specifies whether the database is the source (base) of one or more snapshots. + + .PARAMETER IsDbAccessAdmin + Specifies whether the caller is member of db_accessadmin for this database. + + .PARAMETER IsDbBackupOperator + Specifies whether the caller is member of db_backupoperator. + + .PARAMETER IsDbDataReader + Specifies whether the caller is member of db_datareader. + + .PARAMETER IsDbDataWriter + Specifies whether the caller is member of db_datawriter. + + .PARAMETER IsDbDdlAdmin + Specifies whether the caller is member of db_ddladmin. + + .PARAMETER IsDbDenyDataReader + Specifies whether the caller is member of db_denydatareader. + + .PARAMETER IsDbDenyDataWriter + Specifies whether the caller is member of db_denydatawriter. + + .PARAMETER IsDbManager + Specifies whether the caller is member of db_manager (Azure role). + + .PARAMETER IsDbOwner + Specifies whether the caller is member of db_owner. + + .PARAMETER IsDbSecurityAdmin + Specifies whether the caller is member of db_securityadmin. + + .PARAMETER IsFabricDatabase + Specifies whether the database is a Microsoft Fabric SQL database. + + .PARAMETER IsFullTextEnabled + Specifies whether full-text search is enabled. + + .PARAMETER IsLedger + Specifies whether the database is enabled for SQL Ledger features. + + .PARAMETER IsLoginManager + Specifies whether the caller is member of the login manager role (Azure). + + .PARAMETER IsMailHost + Specifies whether Database Mail host features are configured on this database. + + .PARAMETER IsManagementDataWarehouse + Specifies whether the database is configured as the Management Data Warehouse. + + .PARAMETER IsMaxSizeApplicable + Specifies whether MaxSizeInBytes is enforced for the database. + + .PARAMETER IsMirroringEnabled + Specifies whether database mirroring is configured. + + .PARAMETER IsParameterizationForced + Specifies whether parameterization of queries is forced by default (when ON). + + .PARAMETER IsReadCommittedSnapshotOn + Specifies whether READ_COMMITTED_SNAPSHOT isolation is ON. + + .PARAMETER IsSqlDw + Specifies whether the database is an Azure Synapse (SQL DW) database. + + .PARAMETER IsSqlDwEdition + Specifies whether the edition corresponds to Azure Synapse (DW). + + .PARAMETER IsSystemObject + Specifies whether the database is a system database (master, model, msdb, tempdb). + + .PARAMETER IsVarDecimalStorageFormatEnabled + Specifies whether vardecimal storage format is enabled. + + .PARAMETER IsVarDecimalStorageFormatSupported + Specifies whether vardecimal storage format is supported by the server. + + .PARAMETER LastBackupDate + Specifies the timestamp of the last full database backup. + + .PARAMETER LastDifferentialBackupDate + Specifies the timestamp of the last differential backup. + + .PARAMETER LastGoodCheckDbTime + Specifies the timestamp when DBCC CHECKDB last completed successfully. + + .PARAMETER LastLogBackupDate + Specifies the timestamp of the last transaction log backup. + + .PARAMETER LegacyCardinalityEstimation + Specifies whether the legacy cardinality estimator is enabled for the primary. + + .PARAMETER LegacyCardinalityEstimationForSecondary + Specifies whether the legacy cardinality estimator is enabled for secondary replicas. + + .PARAMETER LocalCursorsDefault + Specifies whether cursors are local by default instead of global (when ON). + + .PARAMETER LogReuseWaitStatus + Specifies the reason why the transaction log cannot be reused. + + .PARAMETER MaxDop + Specifies the MAXDOP database-scoped configuration for primary replicas. + + .PARAMETER MaxDopForSecondary + Specifies the MAXDOP database-scoped configuration for secondary replicas. + + .PARAMETER MaxSizeInBytes + Specifies the maximum size of the database in bytes. + + .PARAMETER MemoryAllocatedToMemoryOptimizedObjectsInKB + Specifies the memory allocated to memory-optimized objects (KB). + + .PARAMETER MemoryUsedByMemoryOptimizedObjectsInKB + Specifies the memory used by memory-optimized objects (KB). + + .PARAMETER MirroringFailoverLogSequenceNumber + Specifies the mirroring failover LSN (if mirroring configured). + + .PARAMETER MirroringID + Specifies the unique mirroring ID for the database. + + .PARAMETER MirroringPartner + Specifies the mirroring partner server name (if configured). + + .PARAMETER MirroringPartnerInstance + Specifies the mirroring partner instance name (if configured). + + .PARAMETER MirroringRedoQueueMaxSize + Specifies the redo queue maximum size for mirroring/AGs. + + .PARAMETER MirroringRoleSequence + Specifies the sequence number for mirroring role transitions. + + .PARAMETER MirroringSafetyLevel + Specifies the mirroring safety level (FULL/Off/HighPerformance). + + .PARAMETER MirroringSafetySequence + Specifies the sequence for mirroring safety changes. + + .PARAMETER MirroringStatus + Specifies the current mirroring state of the database. + + .PARAMETER MirroringTimeout + Specifies the timeout in seconds for mirroring sessions. + + .PARAMETER MirroringWitness + Specifies the mirroring witness server (if used). + + .PARAMETER MirroringWitnessStatus + Specifies the status of the mirroring witness. + + .PARAMETER NestedTriggersEnabled + Specifies whether triggers are allowed to fire other triggers (nested triggers). + + .PARAMETER NumericRoundAbortEnabled + Specifies whether an error is raised on loss of precision due to rounding (when ON). + + .PARAMETER PageVerify + Specifies the page verification setting (NONE, TORN_PAGE_DETECTION, CHECKSUM). + + .PARAMETER ParameterSniffing + Specifies whether parameter sniffing behavior is enabled on the primary. + + .PARAMETER ParameterSniffingForSecondary + Specifies whether parameter sniffing is enabled on secondary replicas. + + .PARAMETER PersistentVersionStoreFileGroup + Specifies the filegroup used for the Persistent Version Store (PVS). + + .PARAMETER PersistentVersionStoreSizeKB + Specifies the size of the Persistent Version Store in KB. + + .PARAMETER PrimaryFilePath + Specifies the path of the primary data files directory. + + .PARAMETER QueryOptimizerHotfixes + Specifies whether query optimizer hotfixes are enabled on the primary. + + .PARAMETER QueryOptimizerHotfixesForSecondary + Specifies whether query optimizer hotfixes are enabled on secondary replicas. + + .PARAMETER QuotedIdentifiersEnabled + Specifies whether identifiers can be delimited by double quotes (when ON). + + .PARAMETER RecoveryForkGuid + Specifies the GUID for the current recovery fork of the database. + + .PARAMETER RecursiveTriggersEnabled + Specifies whether a trigger is allowed to fire itself recursively. + + .PARAMETER RemoteDataArchiveCredential + Specifies the credential name for Stretch Database/remote data archive. + + .PARAMETER RemoteDataArchiveEnabled + Specifies whether Stretch Database (remote data archive) is enabled. + + .PARAMETER RemoteDataArchiveEndpoint + Specifies the endpoint URL for remote data archive. + + .PARAMETER RemoteDataArchiveLinkedServer + Specifies the linked server used by remote data archive. + + .PARAMETER RemoteDataArchiveUseFederatedServiceAccount + Specifies whether to use federated service account for remote data archive. + + .PARAMETER RemoteDatabaseName + Specifies the remote database name for remote data archive. + + .PARAMETER ReplicationOptions + Specifies the replication options that are enabled for the database. + + .PARAMETER ServiceBrokerGuid + Specifies the unique Service Broker identifier for the database. + + .PARAMETER Size + Specifies the approximate size of the database in MB (as reported by SMO). + + .PARAMETER SnapshotIsolationState + Specifies whether SNAPSHOT isolation is OFF/ON/IN_TRANSITION. + + .PARAMETER SpaceAvailable + Specifies the free space available in the database (KB). + + .PARAMETER State + Specifies the general state of the SMO object. + + .PARAMETER Status + Specifies the operational status of the database as reported by SMO. + + .PARAMETER TargetRecoveryTime + Specifies the target recovery time (seconds) for indirect checkpointing. + + .PARAMETER TemporalHistoryRetentionEnabled + Specifies whether automatic cleanup of system-versioned temporal history is enabled. + + .PARAMETER TransformNoiseWords + Specifies how full-text noise word behavior is controlled during queries. + + .PARAMETER TwoDigitYearCutoff + Specifies the two-digit year cutoff used for date conversion. + + .PARAMETER UserAccess + Specifies the database user access mode (MULTI_USER, RESTRICTED_USER, SINGLE_USER). + + .PARAMETER UserName + Specifies the user name for the current connection context (as seen by SMO). + + .PARAMETER Version + Specifies the internal version number of the database. + + .PARAMETER WarnOnRename + Specifies whether a warning is emitted when objects are renamed. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscDatabaseProperty -ServerObject $serverObject -Name 'MyDatabase' -Collation 'SQL_Latin1_General_CP1_CI_AS' + + Tests if the database named **MyDatabase** has the specified collation. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $databaseObject = $serverObject | Get-SqlDscDatabase -Name 'MyDatabase' + Test-SqlDscDatabaseProperty -DatabaseObject $databaseObject -RecoveryModel 'Simple' -CompatibilityLevel 'Version160' + + Tests if the database has the specified recovery model and compatibility level using a database object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscDatabaseProperty -ServerObject $serverObject -Name 'MyDatabase' -Owner 'sa' -AutoClose $false -Trustworthy $false + + Tests multiple database properties at once. + + .INPUTS + `[Microsoft.SqlServer.Management.Smo.Database]` + + The database object to test properties for (from Get-SqlDscDatabase). + + .OUTPUTS + `[System.Boolean]` +#> +function Test-SqlDscDatabaseProperty +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'This is not a password but a credential name reference.')] + [OutputType([System.Boolean])] + [CmdletBinding(DefaultParameterSetName = 'ServerObjectSet')] + param + ( + [Parameter(ParameterSetName = 'ServerObjectSet', Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'ServerObjectSet', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(ParameterSetName = 'DatabaseObjectSet', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Database] + $DatabaseObject, + + # Boolean Properties + [Parameter()] + [System.Boolean] + $AcceleratedRecoveryEnabled, + + [Parameter()] + [System.Boolean] + $ActiveDirectory, + + [Parameter()] + [System.Boolean] + $AnsiNullDefault, + + [Parameter()] + [System.Boolean] + $AnsiNullsEnabled, + + [Parameter()] + [System.Boolean] + $AnsiPaddingEnabled, + + [Parameter()] + [System.Boolean] + $AnsiWarningsEnabled, + + [Parameter()] + [System.Boolean] + $ArithmeticAbortEnabled, + + [Parameter()] + [System.Boolean] + $AutoClose, + + [Parameter()] + [System.Boolean] + $AutoCreateIncrementalStatisticsEnabled, + + [Parameter()] + [System.Boolean] + $AutoCreateStatisticsEnabled, + + [Parameter()] + [System.Boolean] + $AutoShrink, + + [Parameter()] + [System.Boolean] + $AutoUpdateStatisticsAsync, + + [Parameter()] + [System.Boolean] + $AutoUpdateStatisticsEnabled, + + [Parameter()] + [System.Boolean] + $BrokerEnabled, + + [Parameter()] + [System.Boolean] + $CaseSensitive, + + [Parameter()] + [System.Boolean] + $ChangeTrackingAutoCleanUp, + + [Parameter()] + [System.Boolean] + $ChangeTrackingEnabled, + + [Parameter()] + [System.Boolean] + $CloseCursorsOnCommitEnabled, + + [Parameter()] + [System.Boolean] + $ConcatenateNullYieldsNull, + + [Parameter()] + [System.Boolean] + $DatabaseOwnershipChaining, + + [Parameter()] + [System.Boolean] + $DataRetentionEnabled, + + [Parameter()] + [System.Boolean] + $DateCorrelationOptimization, + + [Parameter()] + [System.Boolean] + $DelayedDurability, + + [Parameter()] + [System.Boolean] + $EncryptionEnabled, + + [Parameter()] + [System.Boolean] + $HasDatabaseEncryptionKey, + + [Parameter()] + [System.Boolean] + $HasFileInCloud, + + [Parameter()] + [System.Boolean] + $HasMemoryOptimizedObjects, + + [Parameter()] + [System.Boolean] + $HonorBrokerPriority, + + [Parameter()] + [System.Boolean] + $IsAccessible, + + [Parameter()] + [System.Boolean] + $IsDatabaseSnapshot, + + [Parameter()] + [System.Boolean] + $IsDatabaseSnapshotBase, + + [Parameter()] + [System.Boolean] + $IsDbAccessAdmin, + + [Parameter()] + [System.Boolean] + $IsDbBackupOperator, + + [Parameter()] + [System.Boolean] + $IsDbDataReader, + + [Parameter()] + [System.Boolean] + $IsDbDataWriter, + + [Parameter()] + [System.Boolean] + $IsDbDdlAdmin, + + [Parameter()] + [System.Boolean] + $IsDbDenyDataReader, + + [Parameter()] + [System.Boolean] + $IsDbDenyDataWriter, + + [Parameter()] + [System.Boolean] + $IsDbManager, + + [Parameter()] + [System.Boolean] + $IsDbOwner, + + [Parameter()] + [System.Boolean] + $IsDbSecurityAdmin, + + [Parameter()] + [System.Boolean] + $IsFabricDatabase, + + [Parameter()] + [System.Boolean] + $IsFullTextEnabled, + + [Parameter()] + [System.Boolean] + $IsLedger, + + [Parameter()] + [System.Boolean] + $IsLoginManager, + + [Parameter()] + [System.Boolean] + $IsMailHost, + + [Parameter()] + [System.Boolean] + $IsManagementDataWarehouse, + + [Parameter()] + [System.Boolean] + $IsMaxSizeApplicable, + + [Parameter()] + [System.Boolean] + $IsMirroringEnabled, + + [Parameter()] + [System.Boolean] + $IsParameterizationForced, + + [Parameter()] + [System.Boolean] + $IsReadCommittedSnapshotOn, + + [Parameter()] + [System.Boolean] + $IsSqlDw, + + [Parameter()] + [System.Boolean] + $IsSqlDwEdition, + + [Parameter()] + [System.Boolean] + $IsSystemObject, + + [Parameter()] + [System.Boolean] + $IsVarDecimalStorageFormatEnabled, + + [Parameter()] + [System.Boolean] + $IsVarDecimalStorageFormatSupported, + + [Parameter()] + [System.Boolean] + $LegacyCardinalityEstimation, + + [Parameter()] + [System.Boolean] + $LegacyCardinalityEstimationForSecondary, + + [Parameter()] + [System.Boolean] + $LocalCursorsDefault, + + [Parameter()] + [System.Boolean] + $NestedTriggersEnabled, + + [Parameter()] + [System.Boolean] + $NumericRoundAbortEnabled, + + [Parameter()] + [System.Boolean] + $ParameterSniffing, + + [Parameter()] + [System.Boolean] + $ParameterSniffingForSecondary, + + [Parameter()] + [System.Boolean] + $QueryOptimizerHotfixes, + + [Parameter()] + [System.Boolean] + $QueryOptimizerHotfixesForSecondary, + + [Parameter()] + [System.Boolean] + $QuotedIdentifiersEnabled, + + [Parameter()] + [System.Boolean] + $ReadOnly, + + [Parameter()] + [System.Boolean] + $RecursiveTriggersEnabled, + + [Parameter()] + [System.Boolean] + $RemoteDataArchiveEnabled, + + [Parameter()] + [System.Boolean] + $RemoteDataArchiveUseFederatedServiceAccount, + + [Parameter()] + [System.Boolean] + $TemporalHistoryRetentionEnabled, + + [Parameter()] + [System.Boolean] + $TransformNoiseWords, + + [Parameter()] + [System.Boolean] + $Trustworthy, + + [Parameter()] + [System.Boolean] + $WarnOnRename, + + # Integer Properties + [Parameter()] + [System.Int32] + $ActiveConnections, + + [Parameter()] + [System.Int32] + $ChangeTrackingRetentionPeriod, + + [Parameter()] + [System.Int32] + $DefaultFullTextLanguage, + + [Parameter()] + [System.Int32] + $DefaultLanguage, + + [Parameter()] + [System.Int32] + $ID, + + [Parameter()] + [System.Int32] + $MaxDop, + + [Parameter()] + [System.Int32] + $MaxDopForSecondary, + + [Parameter()] + [System.Int32] + $MirroringRedoQueueMaxSize, + + [Parameter()] + [System.Int32] + $MirroringRoleSequence, + + [Parameter()] + [System.Int32] + $MirroringSafetySequence, + + [Parameter()] + [System.Int32] + $MirroringTimeout, + + [Parameter()] + [System.Int32] + $TargetRecoveryTime, + + [Parameter()] + [System.Int32] + $TwoDigitYearCutoff, + + [Parameter()] + [System.Int32] + $Version, + + # Long Integer Properties + [Parameter()] + [System.Int64] + $IndexSpaceUsage, + + [Parameter()] + [System.Int64] + $MaxSizeInBytes, + + [Parameter()] + [System.Int64] + $MemoryAllocatedToMemoryOptimizedObjectsInKB, + + [Parameter()] + [System.Int64] + $MemoryUsedByMemoryOptimizedObjectsInKB, + + [Parameter()] + [System.Int64] + $MirroringFailoverLogSequenceNumber, + + [Parameter()] + [System.Int64] + $PersistentVersionStoreSizeKB, + + [Parameter()] + [System.Int64] + $SpaceAvailable, + + # Double Properties + [Parameter()] + [System.Double] + $Size, + + # String Properties + [Parameter()] + [System.String] + $AvailabilityGroupName, + + [Parameter()] + [System.String] + $AzureServiceObjective, + + [Parameter()] + [System.String] + $CatalogCollation, + + [Parameter()] + [System.String] + $Collation, + + [Parameter()] + [System.String] + $DboLogin, + + [Parameter()] + [System.String] + $DefaultFileGroup, + + [Parameter()] + [System.String] + $DefaultFileStreamFileGroup, + + [Parameter()] + [System.String] + $DefaultFullTextCatalog, + + [Parameter()] + [System.String] + $DefaultSchema, + + [Parameter()] + [System.String] + $FilestreamDirectoryName, + + [Parameter()] + [System.String] + $MirroringPartner, + + [Parameter()] + [System.String] + $MirroringPartnerInstance, + + [Parameter()] + [System.String] + $MirroringWitness, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PersistentVersionStoreFileGroup, + + [Parameter()] + [System.String] + $PrimaryFilePath, + + [Parameter()] + [System.String] + $RemoteDataArchiveCredential, + + [Parameter()] + [System.String] + $RemoteDataArchiveEndpoint, + + [Parameter()] + [System.String] + $RemoteDataArchiveLinkedServer, + + [Parameter()] + [System.String] + $RemoteDatabaseName, + + [Parameter()] + [System.String] + $UserName, + + [Parameter()] + [System.String] + $AzureEdition, + + # DateTime Properties + [Parameter()] + [System.DateTime] + $CreateDate, + + [Parameter()] + [System.DateTime] + $LastBackupDate, + + [Parameter()] + [System.DateTime] + $LastDifferentialBackupDate, + + [Parameter()] + [System.DateTime] + $LastGoodCheckDbTime, + + [Parameter()] + [System.DateTime] + $LastLogBackupDate, + + # GUID Properties + [Parameter()] + [System.Guid] + $DatabaseGuid, + + [Parameter()] + [System.Guid] + $MirroringID, + + [Parameter()] + [System.Guid] + $RecoveryForkGuid, + + [Parameter()] + [System.Guid] + $ServiceBrokerGuid, + + # Enum Properties (as strings for simplicity) + [Parameter()] + [Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState] + $AvailabilityDatabaseSynchronizationState, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.RetentionPeriodUnits] + $ChangeTrackingRetentionPeriodUnits, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.CompatibilityLevel] + $CompatibilityLevel, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.ContainmentType] + $ContainmentType, + + [Parameter()] + [Microsoft.SqlServer.Management.Common.DatabaseEngineEdition] + $DatabaseEngineEdition, + + [Parameter()] + [Microsoft.SqlServer.Management.Common.DatabaseEngineType] + $DatabaseEngineType, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.FilestreamNonTransactedAccessType] + $FilestreamNonTransactedAccess, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.LogReuseWaitStatus] + $LogReuseWaitStatus, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.MirroringSafetyLevel] + $MirroringSafetyLevel, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.MirroringStatus] + $MirroringStatus, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.MirroringWitnessStatus] + $MirroringWitnessStatus, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.PageVerify] + $PageVerify, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.RecoveryModel] + $RecoveryModel, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.ReplicationOptions] + $ReplicationOptions, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.SnapshotIsolationState] + $SnapshotIsolationState, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.SqlSmoState] + $State, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.DatabaseStatus] + $Status, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.DatabaseUserAccess] + $UserAccess + ) + + process + { + # Get the database object based on the parameter set + switch ($PSCmdlet.ParameterSetName) + { + 'ServerObjectSet' + { + Write-Verbose -Message ($script:localizedData.DatabaseProperty_TestingProperties -f $Name, $ServerObject.InstanceName) + + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + $sqlDatabaseObject = $ServerObject | Get-SqlDscDatabase -Name $Name -ErrorAction 'Stop' + $ErrorActionPreference = $previousErrorActionPreference + } + + 'DatabaseObjectSet' + { + Write-Verbose -Message ($script:localizedData.DatabaseProperty_TestingPropertiesFromObject -f $DatabaseObject.Name, $DatabaseObject.Parent.InstanceName) + + $sqlDatabaseObject = $DatabaseObject + } + } + + $isInDesiredState = $true + + # Remove common parameters and function-specific parameters, leaving only database properties + $boundParameters = Remove-CommonParameter -Hashtable $PSBoundParameters + + # Remove function-specific parameters + foreach ($parameterToRemove in @('ServerObject', 'Name', 'DatabaseObject')) + { + $boundParameters.Remove($parameterToRemove) + } + + # Test each specified property + foreach ($parameterName in $boundParameters.Keys) + { + if ($sqlDatabaseObject.PSObject.Properties.Name -notcontains $parameterName) + { + Write-Error -Message ($script:localizedData.DatabaseProperty_PropertyNotFound -f $parameterName, $sqlDatabaseObject.Name) -Category 'InvalidArgument' -ErrorId 'TSDDP0001' -TargetObject $parameterName + continue + } + + $expectedValue = $boundParameters.$parameterName + $actualValue = $sqlDatabaseObject.$parameterName + + # Use a robust comparison that handles empty strings, nulls, and different types + $valuesMatch = $false + + # Check if both values are null or empty strings (treat them as equivalent) + $actualIsNullOrEmpty = [System.String]::IsNullOrEmpty($actualValue) + $expectedIsNullOrEmpty = [System.String]::IsNullOrEmpty($expectedValue) + + if ($actualIsNullOrEmpty -and $expectedIsNullOrEmpty) + { + # Both are null or empty, consider them equal + $valuesMatch = $true + } + elseif ($actualIsNullOrEmpty -or $expectedIsNullOrEmpty) + { + # One is null/empty and the other is not + $valuesMatch = $false + } + else + { + # Both have values, compare them directly + $valuesMatch = $actualValue -eq $expectedValue + } + + if (-not $valuesMatch) + { + Write-Verbose -Message ($script:localizedData.DatabaseProperty_PropertyWrong -f $sqlDatabaseObject.Name, $parameterName, $actualValue, $expectedValue) + $isInDesiredState = $false + } + else + { + Write-Verbose -Message ($script:localizedData.DatabaseProperty_PropertyCorrect -f $sqlDatabaseObject.Name, $parameterName, $actualValue) + } + } + + return $isInDesiredState + } +} diff --git a/source/Public/Uninstall-SqlDscReportingService.ps1 b/source/Public/Uninstall-SqlDscReportingService.ps1 index 5143763b86..a634585c1c 100644 --- a/source/Public/Uninstall-SqlDscReportingService.ps1 +++ b/source/Public/Uninstall-SqlDscReportingService.ps1 @@ -15,7 +15,7 @@ By default log files are created under %TEMP%. .PARAMETER SuppressRestart - Suppresses the restart of the computer after the uninstallation is finished. + Specifies whether to suppress the restart of the computer after the uninstallation is finished. By default the computer is restarted after the uninstallation is finished. .PARAMETER Timeout @@ -24,11 +24,11 @@ this time, an exception will be thrown. .PARAMETER Force - If specified the command will not ask for confirmation. Same as if Confirm:$false + Specifies whether the command will not ask for confirmation. Same as if '-Confirm:$false' is used. .PARAMETER PassThru - If specified the command will return the setup process exit code. + Specifies whether the command will return the setup process exit code. .OUTPUTS When PassThru is specified the function will return the setup process exit diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 70d9aef193..f934639d13 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -398,6 +398,13 @@ ConvertFrom-StringData @' Database_RecoveryModelWrong = The database '{0}' exists and has the recovery model '{1}', but expected it to have the recovery model '{2}'. Database_OwnerNameWrong = The database '{0}' exists and has the owner '{1}', but expected it to have the owner '{2}'. + ## Test-SqlDscDatabaseProperty + DatabaseProperty_TestingProperties = Testing properties of database '{0}' on instance '{1}'. (TSDDP0001) + DatabaseProperty_TestingPropertiesFromObject = Testing properties of database '{0}' on instance '{1}' using database object. (TSDDP0002) + DatabaseProperty_PropertyWrong = The database '{0}' property '{1}' has the value '{2}', but expected it to have the value '{3}'. (TSDDP0003) + DatabaseProperty_PropertyCorrect = The database '{0}' property '{1}' has the expected value '{2}'. (TSDDP0004) + DatabaseProperty_PropertyNotFound = The property '{0}' does not exist on database '{1}'. This might be due to the property not being supported on this SQL Server version. (TSDDP0005) + ## Set-SqlDscDatabaseDefault DatabaseDefault_Set = Setting default objects of database '{0}' on instance '{1}'. (SSDDD0001) DatabaseDefault_Updated = Database '{0}' default objects were updated successfully. (SSDDD0002) diff --git a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 index 4f471d8765..7c79fb42dc 100644 --- a/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 +++ b/tests/Integration/Commands/New-SqlDscDatabase.Integration.Tests.ps1 @@ -121,4 +121,33 @@ Describe 'New-SqlDscDatabase' -Tag @('Integration_SQL2017', 'Integration_SQL2019 $result.Name | Should -Be $script:refreshTestDbName } } + + Context 'When creating a persistent database for other integration tests' { + BeforeAll { + $script:persistentTestDatabase = 'SqlDscIntegrationTestDatabase_Persistent' + } + + It 'Should create a persistent database that remains on the instance' { + # Create persistent database with Simple recovery model that will remain on the instance + $result = New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentTestDatabase -RecoveryModel 'Simple' -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be $script:persistentTestDatabase + $result.RecoveryModel | Should -Be 'Simple' + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + + # Verify the database exists + $verifyDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentTestDatabase -Refresh -ErrorAction 'Stop' + + $verifyDb | Should -Not -BeNullOrEmpty + $verifyDb.Name | Should -Be $script:persistentTestDatabase + $verifyDb.RecoveryModel | Should -Be 'Simple' + } + + It 'Should throw error when trying to create the persistent database that already exists' { + # Try to re-create the persistent database which should already exist + { New-SqlDscDatabase -ServerObject $script:serverObject -Name $script:persistentTestDatabase -Force -ErrorAction 'Stop' } | + Should -Throw + } + } } diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 2be2b2b840..8b32058254 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -90,7 +90,7 @@ Deny-SqlDscServerPermission | 4 | 4 (New-SqlDscLogin), 1 (Install-SqlDscServer), Revoke-SqlDscServerPermission | 4 | 4 (New-SqlDscLogin), 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Get-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - ConvertFrom-SqlDscDatabasePermission | 4 | 0 (Prerequisites) | - | - -New-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test databases +New-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | SqlDscIntegrationTestDatabase_Persistent database Set-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Test-SqlDscDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Get-SqlDscDatabasePermission | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test database, Test user @@ -214,6 +214,11 @@ that remains for other tests to validate against. Creates a persistent agent operator `SqlDscIntegrationTestOperator_Persistent` that remains on the instance for other tests to use. +### `New-SqlDscDatabase` + +Creates a persistent database `SqlDscIntegrationTestDatabase_Persistent` +with Simple recovery model that remains on the instance for other tests to use. + ## Dependencies ### SqlServer module @@ -291,6 +296,12 @@ IntegrationTestSqlLogin | P@ssw0rd123! | AlterTrace (Deny) | SQL Server login cr Role | Owner | Permission | Description --- | --- | --- | --- SqlDscIntegrationTestRole_Persistent | sa | CreateEndpoint | Server role created by New-SqlDscRole integration tests. CreateEndpoint permission granted by Grant-SqlDscServerPermission integration tests for server permission testing. + +### SQL Server Databases + +Database | Recovery Model | Description +--- | --- | --- +SqlDscIntegrationTestDatabase_Persistent | Simple | Database created by New-SqlDscDatabase integration tests for use by other integration tests. ### Image media (ISO) diff --git a/tests/Integration/Commands/Test-SqlDscDatabaseProperty.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscDatabaseProperty.Integration.Tests.ps1 new file mode 100644 index 0000000000..8a2c176ce2 --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscDatabaseProperty.Integration.Tests.ps1 @@ -0,0 +1,387 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } + + # Persistent test database created by New-SqlDscDatabase integration tests + $script:persistentTestDatabase = 'SqlDscIntegrationTestDatabase_Persistent' + + # Define comprehensive test cases for database properties that can be tested with the persistent test database + $script:testDatabaseTestCases = @( + # Boolean properties - test with expected values for the persistent test database + @{ PropertyName = 'AutoClose'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AutoShrink'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AnsiNullsEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AnsiPaddingEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AnsiWarningsEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ArithmeticAbortEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'BrokerEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'CaseSensitive'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'CloseCursorsOnCommitEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ConcatenateNullYieldsNull'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'DatabaseOwnershipChaining'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'DateCorrelationOptimization'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'EncryptionEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: HasDatabaseEncryptionKey - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'HasDatabaseEncryptionKey'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'HasFileInCloud'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'HasMemoryOptimizedObjects'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsAccessible'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbAccessAdmin'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbBackupOperator'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbDataReader'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbDataWriter'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbDdlAdmin'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbDenyDataReader'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbDenyDataWriter'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbManager'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbOwner'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDbSecurityAdmin'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDatabaseSnapshot'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsDatabaseSnapshotBase'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: IsFabricDatabase - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'IsFabricDatabase'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsFullTextEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: IsLedger - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'IsLedger'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsLoginManager'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsMailHost'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsManagementDataWarehouse'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: IsMaxSizeApplicable - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'IsMaxSizeApplicable'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsMirroringEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsParameterizationForced'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsReadCommittedSnapshotOn'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsSqlDw'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsSqlDwEdition'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsSystemObject'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsVarDecimalStorageFormatEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'IsVarDecimalStorageFormatSupported'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'LocalCursorsDefault'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'NestedTriggersEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'NumericRoundAbortEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'QuotedIdentifiersEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ReadOnly'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'RecursiveTriggersEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'Trustworthy'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AutoCreateStatisticsEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AutoUpdateStatisticsEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AutoUpdateStatisticsAsync'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AutoCreateIncrementalStatisticsEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ParameterSniffing'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'LegacyCardinalityEstimation'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'QueryOptimizerHotfixes'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'AnsiNullDefault'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ChangeTrackingEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'ChangeTrackingAutoCleanUp'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: DataRetentionEnabled - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'DataRetentionEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'TemporalHistoryRetentionEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + # TODO: AcceleratedRecoveryEnabled - Commented out because this property requires specific database configuration or SQL Server edition that is not available in the persistent test database + # @{ PropertyName = 'AcceleratedRecoveryEnabled'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'DelayedDurability'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'HonorBrokerPriority'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + @{ PropertyName = 'TransformNoiseWords'; DatabaseName = $script:persistentTestDatabase; TestValue = $true } + + # String properties - test with actual values from the persistent test database + @{ PropertyName = 'Collation'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'Owner'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'DefaultFileGroup'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'DefaultSchema'; DatabaseName = $script:persistentTestDatabase } + + # Numeric properties - test with actual values from the persistent test database + @{ PropertyName = 'ID'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'ActiveConnections'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'MaxDop'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'TargetRecoveryTime'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'Version'; DatabaseName = $script:persistentTestDatabase } + + # Enum properties - test with actual values from the persistent test database + @{ PropertyName = 'CompatibilityLevel'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'ContainmentType'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'PageVerify'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'RecoveryModel'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'SnapshotIsolationState'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'State'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'Status'; DatabaseName = $script:persistentTestDatabase } + @{ PropertyName = 'UserAccess'; DatabaseName = $script:persistentTestDatabase } + ) +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Test-SqlDscDatabaseProperty' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject -ErrorAction 'Stop' + } + + Context 'When using ServerObjectSet parameter set' { + It 'Should return true when testing master database with no properties specified' { + $result = Test-SqlDscDatabaseProperty -ServerObject $script:serverObject -Name 'master' -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should throw an error when testing non-existent database' { + { Test-SqlDscDatabaseProperty -ServerObject $script:serverObject -Name 'NonExistentDatabase' -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When using DatabaseObjectSet parameter set' { + It 'Should return true when testing database object with no properties specified' { + $databaseObject = $script:serverObject | Get-SqlDscDatabase -Name 'master' -ErrorAction 'Stop' + + $result = Test-SqlDscDatabaseProperty -DatabaseObject $databaseObject -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should accept database object via pipeline' { + $result = $script:serverObject | Get-SqlDscDatabase -Name 'master' -ErrorAction 'Stop' | Test-SqlDscDatabaseProperty -ErrorAction 'Stop' + + $result | Should -BeTrue + } + } + + Context 'When testing different database types' { + It 'Should work correctly with system databases' -ForEach @( + @{ DatabaseName = 'tempdb' } + @{ DatabaseName = 'model' } + @{ DatabaseName = 'msdb' } + ) { + $result = Test-SqlDscDatabaseProperty -ServerObject $script:serverObject -Name $DatabaseName -ErrorAction 'Stop' + + $result | Should -BeTrue + } + } + + Context 'When testing all database properties with persistent test database' { + Context 'When testing Boolean properties' { + It 'Should return expected result when property is tested with value $true' -ForEach ($script:testDatabaseTestCases | Where-Object { $_.TestValue }) { + $databaseObject = $script:serverObject.Databases[$DatabaseName] + $actualValue = $databaseObject.$PropertyName + + $testParameters = @{ + ServerObject = $script:serverObject + Name = $DatabaseName + $PropertyName = $TestValue + ErrorAction = 'Stop' + } + + $result = Test-SqlDscDatabaseProperty @testParameters + + $expectedResult = $actualValue -eq $TestValue + + $result | Should -Be $expectedResult -Because "Property '$PropertyName' should return $expectedResult when testing value '$TestValue' against actual value '$actualValue' for database '$DatabaseName'" + } + + It 'Should return expected result when property is tested with value $false' -ForEach ($script:testDatabaseTestCases | Where-Object { $_.TestValue }) { + $databaseObject = $script:serverObject.Databases[$DatabaseName] + $actualValue = $databaseObject.$PropertyName + $testValue = $false + + $testParameters = @{ + ServerObject = $script:serverObject + Name = $DatabaseName + $PropertyName = $testValue + ErrorAction = 'Stop' + } + + $result = Test-SqlDscDatabaseProperty @testParameters + + $expectedResult = $actualValue -eq $testValue + + $result | Should -Be $expectedResult -Because "Property '$PropertyName' should return $expectedResult when testing value '$testValue' against actual value '$actualValue' for database '$DatabaseName'" + } + } + + Context 'When testing non-Boolean properties' { + It 'Should return true when property matches actual value' -ForEach ($script:testDatabaseTestCases | Where-Object { -not $_.TestValue }) { + $databaseObject = $script:serverObject.Databases[$DatabaseName] + $actualValue = $databaseObject.$PropertyName + + $testParameters = @{ + ServerObject = $script:serverObject + Name = $DatabaseName + $PropertyName = $actualValue + ErrorAction = 'Stop' + } + + if ($actualValue -is [System.Enum]) + { + $testParameters[$PropertyName] = $actualValue.ToString() + } + + $result = Test-SqlDscDatabaseProperty @testParameters + + $result | Should -BeTrue -Because "Property '$PropertyName' should return true when testing value '$actualValue' against actual value '$actualValue' for database '$DatabaseName'" + } + + It 'Should return false when property does not match actual value' -ForEach ($script:testDatabaseTestCases | Where-Object { -not $_.TestValue }) { + $databaseObject = $script:serverObject.Databases[$DatabaseName] + $actualValue = $databaseObject.$PropertyName + + $testValue = if ($actualValue -is [System.Enum]) + { + $enumType = $actualValue.GetType() + $enumValues = [System.Enum]::GetValues($enumType) + $differentEnum = $enumValues | + Where-Object { $_ -ne $actualValue } | Select-Object -First 1 + + if ($differentEnum) + { + $differentEnum.ToString() + } + else + { + # Could not find a different enum value, skip this test + $null + } + } + else + { + if ($null -eq $actualValue) + { + 'DifferentValue' + } + else + { + switch ($actualValue.GetType().Name) + { + 'String' + { + 'DifferentTestValue' + } + + 'Int32' + { + if ($actualValue -eq 0) + { + 999 + } + else + { + 0 + } + } + + 'Int64' + { + if ($actualValue -eq 0) + { + 999 + } + else + { + 0 + } + } + + default + { + 'DifferentValue' + } + } + } + } + + if ($null -eq $testValue -or $testValue -eq $actualValue) + { + Set-ItResult -Skipped -Because "Could not determine a different value for property '$PropertyName' with value '$actualValue'" + return + } + + $testParameters = @{ + ServerObject = $script:serverObject + Name = $DatabaseName + $PropertyName = $testValue + ErrorAction = 'Stop' + } + + $result = Test-SqlDscDatabaseProperty @testParameters + + $result | Should -BeFalse -Because "Property '$PropertyName' should return false when testing value '$testValue' against actual value '$actualValue' for database '$DatabaseName'" + } + } + } + + Context 'When testing comprehensive database property combinations' { + It 'Should return true when testing multiple properties together with correct values' { + # Get actual values from persistent test database + $testDb = $script:serverObject.Databases['SqlDscIntegrationTestDatabase_Persistent'] + $actualCollation = $testDb.Collation + $actualCompatibilityLevel = $testDb.CompatibilityLevel.ToString() + $actualRecoveryModel = $testDb.RecoveryModel.ToString() + $actualOwner = $testDb.Owner + + $result = Test-SqlDscDatabaseProperty -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestDatabase_Persistent' ` + -Collation $actualCollation ` + -RecoveryModel $actualRecoveryModel ` + -Owner $actualOwner ` + -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when testing multiple properties with one incorrect value' { + # Get actual values from persistent test database + $testDb = $script:serverObject.Databases['SqlDscIntegrationTestDatabase_Persistent'] + $actualCollation = $testDb.Collation + $actualOwner = $testDb.Owner + + # Use wrong recovery model + $wrongRecoveryModel = if ($testDb.RecoveryModel.ToString() -eq 'Simple') + { + 'Full' + } + else + { + 'Simple' + } + + $result = Test-SqlDscDatabaseProperty -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestDatabase_Persistent' ` + -Collation $actualCollation ` + -RecoveryModel $wrongRecoveryModel ` + -Owner $actualOwner ` + -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscDatabaseProperty.Tests.ps1 b/tests/Unit/Public/Test-SqlDscDatabaseProperty.Tests.ps1 new file mode 100644 index 0000000000..2ec25d8f67 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscDatabaseProperty.Tests.ps1 @@ -0,0 +1,597 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } + + # Loading mocked classes for discovery time + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + # Define test data for all database properties with sample values (needed at discovery time for ForEach) + $testPropertyData = @{ + # Boolean Properties + 'AcceleratedRecoveryEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'ActiveDirectory' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'AnsiNullDefault' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AnsiNullsEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AnsiPaddingEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AnsiWarningsEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'ArithmeticAbortEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AutoClose' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'AutoCreateIncrementalStatisticsEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AutoCreateStatisticsEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'AutoShrink' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'AutoUpdateStatisticsAsync' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'AutoUpdateStatisticsEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'BrokerEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'CaseSensitive' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'ChangeTrackingAutoCleanUp' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'ChangeTrackingEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'CloseCursorsOnCommitEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'ConcatenateNullYieldsNull' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'DatabaseOwnershipChaining' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'DataRetentionEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'DateCorrelationOptimization' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'DelayedDurability' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'EncryptionEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'HasDatabaseEncryptionKey' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'HasFileInCloud' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'HasMemoryOptimizedObjects' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'HonorBrokerPriority' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsAccessible' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'IsDatabaseSnapshot' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDatabaseSnapshotBase' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbAccessAdmin' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbBackupOperator' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbDatareader' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbDatawriter' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbDdlAdmin' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbDenyDatareader' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbDenyDatawriter' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbManager' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsDbOwner' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'IsDbSecurityAdmin' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsFabricDatabase' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsFullTextEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsLedger' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsLoginManager' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsMailHost' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsManagementDataWarehouse' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsMaxSizeApplicable' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsMirroringEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsParameterizationForced' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsReadCommittedSnapshotOn' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsSqlDw' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsSqlDwEdition' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsSystemObject' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsVarDecimalStorageFormatEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'IsVarDecimalStorageFormatSupported' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'LegacyCardinalityEstimation' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'LegacyCardinalityEstimationForSecondary' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'LocalCursorsDefault' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'NestedTriggersEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'NumericRoundAbortEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'ParameterSniffing' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'ParameterSniffingForSecondary' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'QueryOptimizerHotfixes' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'QueryOptimizerHotfixesForSecondary' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'QuotedIdentifiersEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'ReadOnly' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'RecursiveTriggersEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'RemoteDataArchiveEnabled' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'RemoteDataArchiveUseFederatedServiceAccount' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'TemporalHistoryRetentionEnabled' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + 'TransformNoiseWords' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'Trustworthy' = @{ Type = 'Boolean'; TestValue = $false; ExpectedValue = $false } + 'WarnOnRename' = @{ Type = 'Boolean'; TestValue = $true; ExpectedValue = $true } + + # String Properties + 'AvailabilityGroupName' = @{ Type = 'String'; TestValue = 'TestAG'; ExpectedValue = 'TestAG' } + 'AzureServiceObjective' = @{ Type = 'String'; TestValue = 'S1'; ExpectedValue = 'S1' } + 'CatalogCollation' = @{ Type = 'String'; TestValue = 'SQL_Latin1_General_CP1_CI_AS'; ExpectedValue = 'SQL_Latin1_General_CP1_CI_AS' } + 'Collation' = @{ Type = 'String'; TestValue = 'SQL_Latin1_General_CP1_CI_AS'; ExpectedValue = 'SQL_Latin1_General_CP1_CI_AS' } + 'DboLogin' = @{ Type = 'String'; TestValue = 'sa'; ExpectedValue = 'sa' } + 'DefaultFileGroup' = @{ Type = 'String'; TestValue = 'PRIMARY'; ExpectedValue = 'PRIMARY' } + 'DefaultFileStreamFileGroup' = @{ Type = 'String'; TestValue = 'FileStreamGroup'; ExpectedValue = 'FileStreamGroup' } + 'DefaultFullTextCatalog' = @{ Type = 'String'; TestValue = 'TestCatalog'; ExpectedValue = 'TestCatalog' } + 'DefaultSchema' = @{ Type = 'String'; TestValue = 'dbo'; ExpectedValue = 'dbo' } + 'FilestreamDirectoryName' = @{ Type = 'String'; TestValue = 'TestDirectory'; ExpectedValue = 'TestDirectory' } + 'MirroringPartner' = @{ Type = 'String'; TestValue = 'TestPartner'; ExpectedValue = 'TestPartner' } + 'MirroringPartnerInstance' = @{ Type = 'String'; TestValue = 'TestInstance'; ExpectedValue = 'TestInstance' } + 'MirroringWitness' = @{ Type = 'String'; TestValue = 'TestWitness'; ExpectedValue = 'TestWitness' } + 'Owner' = @{ Type = 'String'; TestValue = 'sa'; ExpectedValue = 'sa' } + 'PersistentVersionStoreFileGroup' = @{ Type = 'String'; TestValue = 'PRIMARY'; ExpectedValue = 'PRIMARY' } + 'PrimaryFilePath' = @{ Type = 'String'; TestValue = 'C:\Data\'; ExpectedValue = 'C:\Data\' } + 'RemoteDataArchiveCredential' = @{ Type = 'String'; TestValue = 'TestCredential'; ExpectedValue = 'TestCredential' } + 'RemoteDataArchiveEndpoint' = @{ Type = 'String'; TestValue = 'https://test.endpoint.com'; ExpectedValue = 'https://test.endpoint.com' } + 'RemoteDataArchiveLinkedServer' = @{ Type = 'String'; TestValue = 'TestLinkedServer'; ExpectedValue = 'TestLinkedServer' } + 'RemoteDatabaseName' = @{ Type = 'String'; TestValue = 'RemoteDB'; ExpectedValue = 'RemoteDB' } + 'UserName' = @{ Type = 'String'; TestValue = 'TestUser'; ExpectedValue = 'TestUser' } + + # Integer Properties + 'ActiveConnections' = @{ Type = 'Int32'; TestValue = 5; ExpectedValue = 5 } + 'ChangeTrackingRetentionPeriod' = @{ Type = 'Int32'; TestValue = 2; ExpectedValue = 2 } + 'DefaultFullTextLanguage' = @{ Type = 'Int32'; TestValue = 1033; ExpectedValue = 1033 } + 'DefaultLanguage' = @{ Type = 'Int32'; TestValue = 0; ExpectedValue = 0 } + 'ID' = @{ Type = 'Int32'; TestValue = 5; ExpectedValue = 5 } + 'MaxDop' = @{ Type = 'Int32'; TestValue = 0; ExpectedValue = 0 } + 'MaxDopForSecondary' = @{ Type = 'Int32'; TestValue = 0; ExpectedValue = 0 } + 'MirroringRedoQueueMaxSize' = @{ Type = 'Int32'; TestValue = 100; ExpectedValue = 100 } + 'MirroringRoleSequence' = @{ Type = 'Int32'; TestValue = 1; ExpectedValue = 1 } + 'MirroringSafetySequence' = @{ Type = 'Int32'; TestValue = 1; ExpectedValue = 1 } + 'MirroringTimeout' = @{ Type = 'Int32'; TestValue = 10; ExpectedValue = 10 } + 'TargetRecoveryTime' = @{ Type = 'Int32'; TestValue = 60; ExpectedValue = 60 } + 'TwoDigitYearCutoff' = @{ Type = 'Int32'; TestValue = 2049; ExpectedValue = 2049 } + 'Version' = @{ Type = 'Int32'; TestValue = 904; ExpectedValue = 904 } + + # Enum Properties + 'AvailabilityDatabaseSynchronizationState' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState]::Synchronized; ExpectedValue = [Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState]::Synchronized } + 'ChangeTrackingRetentionPeriodUnits' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.RetentionPeriodUnits]::Days; ExpectedValue = [Microsoft.SqlServer.Management.Smo.RetentionPeriodUnits]::Days } + 'CompatibilityLevel' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.CompatibilityLevel]::Version150; ExpectedValue = [Microsoft.SqlServer.Management.Smo.CompatibilityLevel]::Version150 } + 'ContainmentType' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.ContainmentType]::None; ExpectedValue = [Microsoft.SqlServer.Management.Smo.ContainmentType]::None } + 'DatabaseEngineEdition' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Common.DatabaseEngineEdition]::Standard; ExpectedValue = [Microsoft.SqlServer.Management.Common.DatabaseEngineEdition]::Standard } + 'DatabaseEngineType' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Common.DatabaseEngineType]::Standalone; ExpectedValue = [Microsoft.SqlServer.Management.Common.DatabaseEngineType]::Standalone } + 'FilestreamNonTransactedAccess' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.FilestreamNonTransactedAccessType]::Off; ExpectedValue = [Microsoft.SqlServer.Management.Smo.FilestreamNonTransactedAccessType]::Off } + 'LogReuseWaitStatus' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.LogReuseWaitStatus]::Nothing; ExpectedValue = [Microsoft.SqlServer.Management.Smo.LogReuseWaitStatus]::Nothing } + 'MirroringSafetyLevel' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.MirroringSafetyLevel]::Full; ExpectedValue = [Microsoft.SqlServer.Management.Smo.MirroringSafetyLevel]::Full } + 'MirroringStatus' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.MirroringStatus]::None; ExpectedValue = [Microsoft.SqlServer.Management.Smo.MirroringStatus]::None } + 'MirroringWitnessStatus' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.MirroringWitnessStatus]::None; ExpectedValue = [Microsoft.SqlServer.Management.Smo.MirroringWitnessStatus]::None } + 'ReplicationOptions' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.ReplicationOptions]::None; ExpectedValue = [Microsoft.SqlServer.Management.Smo.ReplicationOptions]::None } + 'SnapshotIsolationState' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.SnapshotIsolationState]::Disabled; ExpectedValue = [Microsoft.SqlServer.Management.Smo.SnapshotIsolationState]::Disabled } + 'State' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.SqlSmoState]::Existing; ExpectedValue = [Microsoft.SqlServer.Management.Smo.SqlSmoState]::Existing } + 'Status' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.DatabaseStatus]::Normal; ExpectedValue = [Microsoft.SqlServer.Management.Smo.DatabaseStatus]::Normal } + 'PageVerify' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.PageVerify]::Checksum; ExpectedValue = [Microsoft.SqlServer.Management.Smo.PageVerify]::Checksum } + 'RecoveryModel' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Full; ExpectedValue = [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Full } + 'UserAccess' = @{ Type = 'Enum'; TestValue = [Microsoft.SqlServer.Management.Smo.DatabaseUserAccess]::Multiple; ExpectedValue = [Microsoft.SqlServer.Management.Smo.DatabaseUserAccess]::Multiple } + } + + # Create test cases array for ForEach (needed at discovery time) + $script:testCases = @() + foreach ($kvp in $testPropertyData.GetEnumerator()) { + $script:testCases += @{ + PropertyName = $kvp.Key + TestValue = $kvp.Value.TestValue + Type = $kvp.Value.Type + ExpectedValue = $kvp.Value.ExpectedValue + } + } + + # Create smaller test set for mismatch testing (first 10 properties) + $script:mismatchTestCases = @() + $counter = 0 + foreach ($kvp in $testPropertyData.GetEnumerator()) { + if ($counter -ge 10) { break } + + # Create a different test value based on type + $differentValue = switch ($kvp.Value.Type) { + 'Boolean' { -not $kvp.Value.ExpectedValue } + 'String' { 'DifferentValue' } + 'Int32' { $kvp.Value.ExpectedValue + 100 } + 'Enum' { + # For enum types, use a different valid enum value + switch ($kvp.Key) { + 'AvailabilityDatabaseSynchronizationState' { [Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState]::Synchronizing } + 'ChangeTrackingRetentionPeriodUnits' { [Microsoft.SqlServer.Management.Smo.RetentionPeriodUnits]::Hours } + 'CompatibilityLevel' { [Microsoft.SqlServer.Management.Smo.CompatibilityLevel]::Version140 } + 'ContainmentType' { [Microsoft.SqlServer.Management.Smo.ContainmentType]::Partial } + 'DatabaseEngineEdition' { [Microsoft.SqlServer.Management.Common.DatabaseEngineEdition]::Enterprise } + 'DatabaseEngineType' { [Microsoft.SqlServer.Management.Common.DatabaseEngineType]::SqlAzureDatabase } + 'FilestreamNonTransactedAccess' { [Microsoft.SqlServer.Management.Smo.FilestreamNonTransactedAccessType]::ReadOnly } + 'LogReuseWaitStatus' { [Microsoft.SqlServer.Management.Smo.LogReuseWaitStatus]::LogBackup } + 'MirroringSafetyLevel' { [Microsoft.SqlServer.Management.Smo.MirroringSafetyLevel]::Off } + 'MirroringStatus' { [Microsoft.SqlServer.Management.Smo.MirroringStatus]::Suspended } + 'MirroringWitnessStatus' { [Microsoft.SqlServer.Management.Smo.MirroringWitnessStatus]::Disconnected } + 'ReplicationOptions' { [Microsoft.SqlServer.Management.Smo.ReplicationOptions]::Published } + 'SnapshotIsolationState' { [Microsoft.SqlServer.Management.Smo.SnapshotIsolationState]::Enabled } + 'State' { [Microsoft.SqlServer.Management.Smo.SqlSmoState]::Creating } + 'Status' { [Microsoft.SqlServer.Management.Smo.DatabaseStatus]::Offline } + 'PageVerify' { [Microsoft.SqlServer.Management.Smo.PageVerify]::None } + 'RecoveryModel' { [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Simple } + 'UserAccess' { [Microsoft.SqlServer.Management.Smo.DatabaseUserAccess]::Single } + default { 'DifferentValue' } + } + } + default { 'DifferentValue' } + } + + $script:mismatchTestCases += @{ + PropertyName = $kvp.Key + DifferentValue = $differentValue + } + $counter++ + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Test-SqlDscDatabaseProperty' -Tag 'Public' { + Context 'When using ServerObjectSet parameter set' { + BeforeAll { + $mockExistingDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Collation' -Value 'SQL_Latin1_General_CP1_CI_AS' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'CompatibilityLevel' -Value 'Version150' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'RecoveryModel' -Value 'Full' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Owner' -Value 'sa' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'AutoClose' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'AutoShrink' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'ReadOnly' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Trustworthy' -Value $false -Force + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'TestDatabase' = $mockExistingDatabase + } + } -Force + + $mockDatabaseObjectWithParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'Collation' -Value 'SQL_Latin1_General_CP1_CI_AS' -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'CompatibilityLevel' -Value 'Version150' -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'RecoveryModel' -Value 'Full' -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'Owner' -Value 'sa' -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'AutoClose' -Value $false -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'AutoShrink' -Value $false -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'ReadOnly' -Value $false -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'Trustworthy' -Value $false -Force + $mockDatabaseObjectWithParent | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + } + + It 'Should return true when database exists and no properties specified' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' + + $result | Should -BeTrue + } + + It 'Should throw an error when database does not exist' { + { Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'NonExistentDatabase' } | Should -Throw -ExpectedMessage "Database 'NonExistentDatabase' was not found." + } + + It 'Should return true when single property matches' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -Collation 'SQL_Latin1_General_CP1_CI_AS' + + $result | Should -BeTrue + } + + It 'Should return false when single property does not match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -Collation 'Different_Collation' + + $result | Should -BeFalse + } + + It 'Should return true when multiple properties match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -Collation 'SQL_Latin1_General_CP1_CI_AS' -CompatibilityLevel 'Version150' -Owner 'sa' + + $result | Should -BeTrue + } + + It 'Should return false when one of multiple properties does not match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -Collation 'SQL_Latin1_General_CP1_CI_AS' -CompatibilityLevel 'Version140' -Owner 'sa' + + $result | Should -BeFalse + } + + It 'Should return true when boolean property matches' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -AutoClose $false + + $result | Should -BeTrue + } + + It 'Should return false when boolean property does not match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -AutoClose $true + + $result | Should -BeFalse + } + } + + Context 'When using DatabaseObjectSet parameter set' { + BeforeAll { + $mockExistingDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Collation' -Value 'SQL_Latin1_General_CP1_CI_AS' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'CompatibilityLevel' -Value 'Version150' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'RecoveryModel' -Value 'Full' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Owner' -Value 'sa' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'AutoClose' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'AutoShrink' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'ReadOnly' -Value $false -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Trustworthy' -Value $false -Force + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + } + + It 'Should return true when no properties specified' { + $result = Test-SqlDscDatabaseProperty -DatabaseObject $mockExistingDatabase + + $result | Should -BeTrue + } + + It 'Should return true when single property matches' { + $result = Test-SqlDscDatabaseProperty -DatabaseObject $mockExistingDatabase -Collation 'SQL_Latin1_General_CP1_CI_AS' + + $result | Should -BeTrue + } + + It 'Should return false when single property does not match' { + $result = Test-SqlDscDatabaseProperty -DatabaseObject $mockExistingDatabase -Collation 'Different_Collation' + + $result | Should -BeFalse + } + + It 'Should return true when multiple properties match' { + $result = Test-SqlDscDatabaseProperty -DatabaseObject $mockExistingDatabase -Collation 'SQL_Latin1_General_CP1_CI_AS' -CompatibilityLevel 'Version150' -Owner 'sa' + + $result | Should -BeTrue + } + + It 'Should return false when one of multiple properties does not match' { + $result = Test-SqlDscDatabaseProperty -DatabaseObject $mockExistingDatabase -Collation 'SQL_Latin1_General_CP1_CI_AS' -CompatibilityLevel 'Version140' -Owner 'sa' + + $result | Should -BeFalse + } + + It 'Should accept pipeline input' { + $result = $mockExistingDatabase | Test-SqlDscDatabaseProperty -Collation 'SQL_Latin1_General_CP1_CI_AS' + + $result | Should -BeTrue + } + } + + Context 'When testing string properties that may come from enums' { + BeforeAll { + $mockExistingDatabase = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockExistingDatabase | Add-Member -MemberType 'NoteProperty' -Name 'RecoveryModel' -Value 'Full' -Force + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'TestDatabase' = $mockExistingDatabase + } + } -Force + } + + It 'Should handle string properties correctly when they match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -RecoveryModel 'Full' + + $result | Should -BeTrue + } + + It 'Should handle string properties correctly when they do not match' { + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'TestDatabase' -RecoveryModel 'Simple' + + $result | Should -BeFalse + } + } + + Context 'When testing a property that does not exist on the database object' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -PassThru -Force + + # Mock Get-SqlDscDatabase to return our custom minimal database object + Mock -CommandName Get-SqlDscDatabase -MockWith { + <# + Create a minimal database object without certain properties using PSCustomObject + This allows us to control exactly which properties exist, simulating older SQL Server versions + #> + return [PSCustomObject] @{ + Name = 'MinimalDatabase' + Collation = 'SQL_Latin1_General_CP1_CI_AS' + # Intentionally not including AcceleratedRecoveryEnabled to simulate SQL Server version that doesn't support it + } + } + } + + It 'Should throw an exception when property does not exist on the database object' { + # AcceleratedRecoveryEnabled is not added to the minimal database object + { Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'MinimalDatabase' -AcceleratedRecoveryEnabled $true -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage "The property 'AcceleratedRecoveryEnabled' does not exist on database 'MinimalDatabase'. This might be due to the property not being supported on this SQL Server version.*" + } + + It 'Should continue testing other properties after encountering a missing property' { + # Test with both a missing property and an existing property + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'MinimalDatabase' -AcceleratedRecoveryEnabled $true -Collation 'SQL_Latin1_General_CP1_CI_AS' -ErrorAction 'SilentlyContinue' + + # Should return true because the existing property (Collation) matches + $result | Should -BeTrue + } + + It 'Should return false when a missing property is tested along with a non-matching property' { + # Test with a missing property and a non-matching existing property + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObject -Name 'MinimalDatabase' -AcceleratedRecoveryEnabled $true -Collation 'Different_Collation' -ErrorAction 'SilentlyContinue' + + # Should return false because the existing property (Collation) does not match + $result | Should -BeFalse + } + } + + Context 'Parameter validation' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObjectSet' + ExpectedParameters = '-ServerObject -Name [-AcceleratedRecoveryEnabled ] [-ActiveDirectory ] [-AnsiNullDefault ] [-AnsiNullsEnabled ] [-AnsiPaddingEnabled ] [-AnsiWarningsEnabled ] [-ArithmeticAbortEnabled ] [-AutoClose ] [-AutoCreateIncrementalStatisticsEnabled ] [-AutoCreateStatisticsEnabled ] [-AutoShrink ] [-AutoUpdateStatisticsAsync ] [-AutoUpdateStatisticsEnabled ] [-BrokerEnabled ] [-CaseSensitive ] [-ChangeTrackingAutoCleanUp ] [-ChangeTrackingEnabled ] [-CloseCursorsOnCommitEnabled ] [-ConcatenateNullYieldsNull ] [-DatabaseOwnershipChaining ] [-DataRetentionEnabled ] [-DateCorrelationOptimization ] [-DelayedDurability ] [-EncryptionEnabled ] [-HasDatabaseEncryptionKey ] [-HasFileInCloud ] [-HasMemoryOptimizedObjects ] [-HonorBrokerPriority ] [-IsAccessible ] [-IsDatabaseSnapshot ] [-IsDatabaseSnapshotBase ] [-IsDbAccessAdmin ] [-IsDbBackupOperator ] [-IsDbDataReader ] [-IsDbDataWriter ] [-IsDbDdlAdmin ] [-IsDbDenyDataReader ] [-IsDbDenyDataWriter ] [-IsDbManager ] [-IsDbOwner ] [-IsDbSecurityAdmin ] [-IsFabricDatabase ] [-IsFullTextEnabled ] [-IsLedger ] [-IsLoginManager ] [-IsMailHost ] [-IsManagementDataWarehouse ] [-IsMaxSizeApplicable ] [-IsMirroringEnabled ] [-IsParameterizationForced ] [-IsReadCommittedSnapshotOn ] [-IsSqlDw ] [-IsSqlDwEdition ] [-IsSystemObject ] [-IsVarDecimalStorageFormatEnabled ] [-IsVarDecimalStorageFormatSupported ] [-LegacyCardinalityEstimation ] [-LegacyCardinalityEstimationForSecondary ] [-LocalCursorsDefault ] [-NestedTriggersEnabled ] [-NumericRoundAbortEnabled ] [-ParameterSniffing ] [-ParameterSniffingForSecondary ] [-QueryOptimizerHotfixes ] [-QueryOptimizerHotfixesForSecondary ] [-QuotedIdentifiersEnabled ] [-ReadOnly ] [-RecursiveTriggersEnabled ] [-RemoteDataArchiveEnabled ] [-RemoteDataArchiveUseFederatedServiceAccount ] [-TemporalHistoryRetentionEnabled ] [-TransformNoiseWords ] [-Trustworthy ] [-WarnOnRename ] [-ActiveConnections ] [-ChangeTrackingRetentionPeriod ] [-DefaultFullTextLanguage ] [-DefaultLanguage ] [-ID ] [-MaxDop ] [-MaxDopForSecondary ] [-MirroringRedoQueueMaxSize ] [-MirroringRoleSequence ] [-MirroringSafetySequence ] [-MirroringTimeout ] [-TargetRecoveryTime ] [-TwoDigitYearCutoff ] [-Version ] [-IndexSpaceUsage ] [-MaxSizeInBytes ] [-MemoryAllocatedToMemoryOptimizedObjectsInKB ] [-MemoryUsedByMemoryOptimizedObjectsInKB ] [-MirroringFailoverLogSequenceNumber ] [-PersistentVersionStoreSizeKB ] [-SpaceAvailable ] [-Size ] [-AvailabilityGroupName ] [-AzureServiceObjective ] [-CatalogCollation ] [-Collation ] [-DboLogin ] [-DefaultFileGroup ] [-DefaultFileStreamFileGroup ] [-DefaultFullTextCatalog ] [-DefaultSchema ] [-FilestreamDirectoryName ] [-MirroringPartner ] [-MirroringPartnerInstance ] [-MirroringWitness ] [-Owner ] [-PersistentVersionStoreFileGroup ] [-PrimaryFilePath ] [-RemoteDataArchiveCredential ] [-RemoteDataArchiveEndpoint ] [-RemoteDataArchiveLinkedServer ] [-RemoteDatabaseName ] [-UserName ] [-AzureEdition ] [-CreateDate ] [-LastBackupDate ] [-LastDifferentialBackupDate ] [-LastGoodCheckDbTime ] [-LastLogBackupDate ] [-DatabaseGuid ] [-MirroringID ] [-RecoveryForkGuid ] [-ServiceBrokerGuid ] [-AvailabilityDatabaseSynchronizationState ] [-ChangeTrackingRetentionPeriodUnits ] [-CompatibilityLevel ] [-ContainmentType ] [-DatabaseEngineEdition ] [-DatabaseEngineType ] [-FilestreamNonTransactedAccess ] [-LogReuseWaitStatus ] [-MirroringSafetyLevel ] [-MirroringStatus ] [-MirroringWitnessStatus ] [-PageVerify ] [-RecoveryModel ] [-ReplicationOptions ] [-SnapshotIsolationState ] [-State ] [-Status ] [-UserAccess ] []' + } + @{ + ExpectedParameterSetName = 'DatabaseObjectSet' + ExpectedParameters = '-DatabaseObject [-AcceleratedRecoveryEnabled ] [-ActiveDirectory ] [-AnsiNullDefault ] [-AnsiNullsEnabled ] [-AnsiPaddingEnabled ] [-AnsiWarningsEnabled ] [-ArithmeticAbortEnabled ] [-AutoClose ] [-AutoCreateIncrementalStatisticsEnabled ] [-AutoCreateStatisticsEnabled ] [-AutoShrink ] [-AutoUpdateStatisticsAsync ] [-AutoUpdateStatisticsEnabled ] [-BrokerEnabled ] [-CaseSensitive ] [-ChangeTrackingAutoCleanUp ] [-ChangeTrackingEnabled ] [-CloseCursorsOnCommitEnabled ] [-ConcatenateNullYieldsNull ] [-DatabaseOwnershipChaining ] [-DataRetentionEnabled ] [-DateCorrelationOptimization ] [-DelayedDurability ] [-EncryptionEnabled ] [-HasDatabaseEncryptionKey ] [-HasFileInCloud ] [-HasMemoryOptimizedObjects ] [-HonorBrokerPriority ] [-IsAccessible ] [-IsDatabaseSnapshot ] [-IsDatabaseSnapshotBase ] [-IsDbAccessAdmin ] [-IsDbBackupOperator ] [-IsDbDataReader ] [-IsDbDataWriter ] [-IsDbDdlAdmin ] [-IsDbDenyDataReader ] [-IsDbDenyDataWriter ] [-IsDbManager ] [-IsDbOwner ] [-IsDbSecurityAdmin ] [-IsFabricDatabase ] [-IsFullTextEnabled ] [-IsLedger ] [-IsLoginManager ] [-IsMailHost ] [-IsManagementDataWarehouse ] [-IsMaxSizeApplicable ] [-IsMirroringEnabled ] [-IsParameterizationForced ] [-IsReadCommittedSnapshotOn ] [-IsSqlDw ] [-IsSqlDwEdition ] [-IsSystemObject ] [-IsVarDecimalStorageFormatEnabled ] [-IsVarDecimalStorageFormatSupported ] [-LegacyCardinalityEstimation ] [-LegacyCardinalityEstimationForSecondary ] [-LocalCursorsDefault ] [-NestedTriggersEnabled ] [-NumericRoundAbortEnabled ] [-ParameterSniffing ] [-ParameterSniffingForSecondary ] [-QueryOptimizerHotfixes ] [-QueryOptimizerHotfixesForSecondary ] [-QuotedIdentifiersEnabled ] [-ReadOnly ] [-RecursiveTriggersEnabled ] [-RemoteDataArchiveEnabled ] [-RemoteDataArchiveUseFederatedServiceAccount ] [-TemporalHistoryRetentionEnabled ] [-TransformNoiseWords ] [-Trustworthy ] [-WarnOnRename ] [-ActiveConnections ] [-ChangeTrackingRetentionPeriod ] [-DefaultFullTextLanguage ] [-DefaultLanguage ] [-ID ] [-MaxDop ] [-MaxDopForSecondary ] [-MirroringRedoQueueMaxSize ] [-MirroringRoleSequence ] [-MirroringSafetySequence ] [-MirroringTimeout ] [-TargetRecoveryTime ] [-TwoDigitYearCutoff ] [-Version ] [-IndexSpaceUsage ] [-MaxSizeInBytes ] [-MemoryAllocatedToMemoryOptimizedObjectsInKB ] [-MemoryUsedByMemoryOptimizedObjectsInKB ] [-MirroringFailoverLogSequenceNumber ] [-PersistentVersionStoreSizeKB ] [-SpaceAvailable ] [-Size ] [-AvailabilityGroupName ] [-AzureServiceObjective ] [-CatalogCollation ] [-Collation ] [-DboLogin ] [-DefaultFileGroup ] [-DefaultFileStreamFileGroup ] [-DefaultFullTextCatalog ] [-DefaultSchema ] [-FilestreamDirectoryName ] [-MirroringPartner ] [-MirroringPartnerInstance ] [-MirroringWitness ] [-Owner ] [-PersistentVersionStoreFileGroup ] [-PrimaryFilePath ] [-RemoteDataArchiveCredential ] [-RemoteDataArchiveEndpoint ] [-RemoteDataArchiveLinkedServer ] [-RemoteDatabaseName ] [-UserName ] [-AzureEdition ] [-CreateDate ] [-LastBackupDate ] [-LastDifferentialBackupDate ] [-LastGoodCheckDbTime ] [-LastLogBackupDate ] [-DatabaseGuid ] [-MirroringID ] [-RecoveryForkGuid ] [-ServiceBrokerGuid ] [-AvailabilityDatabaseSynchronizationState ] [-ChangeTrackingRetentionPeriodUnits ] [-CompatibilityLevel ] [-ContainmentType ] [-DatabaseEngineEdition ] [-DatabaseEngineType ] [-FilestreamNonTransactedAccess ] [-LogReuseWaitStatus ] [-MirroringSafetyLevel ] [-MirroringStatus ] [-MirroringWitnessStatus ] [-PageVerify ] [-RecoveryModel ] [-ReplicationOptions ] [-SnapshotIsolationState ] [-State ] [-Status ] [-UserAccess ] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscDatabaseProperty').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscDatabaseProperty').Parameters['ServerObject'] + + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscDatabaseProperty').Parameters['Name'] + + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have DatabaseObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscDatabaseProperty').Parameters['DatabaseObject'] + + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + } + + Context 'When testing database property parameters' { + BeforeAll { + # Create mock database using the SMO stub - properties are already set to expected values + $mockDatabaseWithAllProperties = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseWithAllProperties.Name = 'TestDatabase' + + $mockServerObjectForAll = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObjectForAll.InstanceName = 'TestInstance' + $mockServerObjectForAll | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'TestDatabase' = $mockDatabaseWithAllProperties + } + } -Force + } + + It 'Should support all database property parameters' { + $command = Get-Command -Name 'Test-SqlDscDatabaseProperty' + + # Test that all expected parameters are available (using the testPropertyData from BeforeDiscovery) + $expectedParameters = @( + 'AcceleratedRecoveryEnabled', 'ActiveDirectory', 'AnsiNullDefault', 'AnsiNullsEnabled', 'AnsiPaddingEnabled', + 'AnsiWarningsEnabled', 'ArithmeticAbortEnabled', 'AutoClose', 'AutoCreateIncrementalStatisticsEnabled', + 'AutoCreateStatisticsEnabled', 'AutoShrink', 'AutoUpdateStatisticsAsync', 'AutoUpdateStatisticsEnabled', + 'BrokerEnabled', 'CaseSensitive', 'ChangeTrackingAutoCleanUp', 'ChangeTrackingEnabled', + 'CloseCursorsOnCommitEnabled', 'ConcatenateNullYieldsNull', 'DatabaseOwnershipChaining', + 'DataRetentionEnabled', 'DateCorrelationOptimization', 'DelayedDurability', 'EncryptionEnabled', + 'HasDatabaseEncryptionKey', 'HasFileInCloud', 'HasMemoryOptimizedObjects', 'HonorBrokerPriority', + 'IsAccessible', 'IsDatabaseSnapshot', 'IsDatabaseSnapshotBase', 'IsDbAccessAdmin', 'IsDbBackupOperator', + 'IsDbDataReader', 'IsDbDataWriter', 'IsDbDdlAdmin', 'IsDbDenyDataReader', 'IsDbDenyDataWriter', + 'IsDbManager', 'IsDbOwner', 'IsDbSecurityAdmin', 'IsFabricDatabase', 'IsFullTextEnabled', + 'IsLedger', 'IsLoginManager', 'IsMailHost', 'IsManagementDataWarehouse', 'IsMaxSizeApplicable', + 'IsMirroringEnabled', 'IsParameterizationForced', 'IsReadCommittedSnapshotOn', 'IsSqlDw', + 'IsSqlDwEdition', 'IsSystemObject', 'IsVarDecimalStorageFormatEnabled', 'IsVarDecimalStorageFormatSupported', + 'LegacyCardinalityEstimation', 'LegacyCardinalityEstimationForSecondary', 'LocalCursorsDefault', + 'NestedTriggersEnabled', 'NumericRoundAbortEnabled', 'ParameterSniffing', 'ParameterSniffingForSecondary', + 'QueryOptimizerHotfixes', 'QueryOptimizerHotfixesForSecondary', 'QuotedIdentifiersEnabled', 'ReadOnly', + 'RecursiveTriggersEnabled', 'RemoteDataArchiveEnabled', 'RemoteDataArchiveUseFederatedServiceAccount', + 'TemporalHistoryRetentionEnabled', 'TransformNoiseWords', 'Trustworthy', 'WarnOnRename', + 'AvailabilityGroupName', 'AzureServiceObjective', 'CatalogCollation', 'Collation', 'DboLogin', + 'DefaultFileGroup', 'DefaultFileStreamFileGroup', 'DefaultFullTextCatalog', 'DefaultSchema', + 'FilestreamDirectoryName', 'MirroringPartner', 'MirroringPartnerInstance', 'MirroringWitness', + 'Name', 'Owner', 'PersistentVersionStoreFileGroup', 'PrimaryFilePath', 'RemoteDataArchiveCredential', + 'RemoteDataArchiveEndpoint', 'RemoteDataArchiveLinkedServer', 'RemoteDatabaseName', 'UserName', + 'ActiveConnections', 'ChangeTrackingRetentionPeriod', 'DefaultFullTextLanguage', 'DefaultLanguage', + 'ID', 'MaxDop', 'MaxDopForSecondary', 'MirroringRedoQueueMaxSize', 'MirroringRoleSequence', + 'MirroringSafetySequence', 'MirroringTimeout', 'TargetRecoveryTime', 'TwoDigitYearCutoff', 'Version', + 'AvailabilityDatabaseSynchronizationState', 'ChangeTrackingRetentionPeriodUnits', 'CompatibilityLevel', 'ContainmentType', 'FilestreamNonTransactedAccess', 'PageVerify', 'RecoveryModel', 'UserAccess' + ) + + foreach ($parameterName in $expectedParameters) { + $command.Parameters.Keys | Should -Contain $parameterName -Because "Parameter '$parameterName' should be available" + } + } + + It 'Should return true when property matches expected value' -ForEach $script:testCases { + # Create parameter hashtable + $params = @{ + ServerObject = $mockServerObjectForAll + Name = 'TestDatabase' + $PropertyName = $TestValue + } + + $result = Test-SqlDscDatabaseProperty @params + $result | Should -BeTrue -Because "Property '$PropertyName' with value '$TestValue' should match" + } + + It 'Should return false when property does not match expected value' -ForEach $script:mismatchTestCases { + # Create parameter hashtable with different value + $params = @{ + ServerObject = $mockServerObjectForAll + Name = 'TestDatabase' + $PropertyName = $DifferentValue + } + + $result = Test-SqlDscDatabaseProperty @params + $result | Should -BeFalse -Because "Property '$PropertyName' with different value should not match" + } + + It 'Should test multiple properties together and return true when all match' { + # Test a combination of different property types + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObjectForAll -Name 'TestDatabase' ` + -Collation 'SQL_Latin1_General_CP1_CI_AS' ` + -AutoClose $false ` + -MaxDop 0 ` + -RecoveryModel 'Full' ` + -Owner 'sa' + + $result | Should -BeTrue + } + + It 'Should test multiple properties together and return false when one does not match' { + # Test a combination where one property doesn't match + $result = Test-SqlDscDatabaseProperty -ServerObject $mockServerObjectForAll -Name 'TestDatabase' ` + -Collation 'SQL_Latin1_General_CP1_CI_AS' ` + -AutoClose $true ` + -MaxDop 0 ` + -RecoveryModel 'Full' ` + -Owner 'sa' + + $result | Should -BeFalse + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index d10401ebef..3f65099eae 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -174,6 +174,142 @@ public enum DatabaseUserAccess : int Restricted = 2 } + // Database-specific enums + public enum CompatibilityLevel : int + { + Version60 = 60, + Version65 = 65, + Version70 = 70, + Version80 = 80, + Version90 = 90, + Version100 = 100, + Version110 = 110, + Version120 = 120, + Version130 = 130, + Version140 = 140, + Version150 = 150, + Version160 = 160, + Version170 = 170 + } + + public enum ContainmentType : int + { + None = 0, + Partial = 1 + } + + public enum FilestreamNonTransactedAccessType : int + { + Off = 0, + ReadOnly = 1, + Full = 2 + } + + public enum PageVerify : int + { + None = 0, + TornPageDetection = 1, + Checksum = 2 + } + + public enum RecoveryModel : int + { + Full = 1, + BulkLogged = 2, + Simple = 3 + } + + public enum RetentionPeriodUnits : int + { + None = 0, + Days = 1, + Hours = 2, + Minutes = 3 + } + + public enum AvailabilityDatabaseSynchronizationState : int + { + NotSynchronizing = 0, + Synchronizing = 1, + Synchronized = 2, + Reverting = 3, + Initializing = 4 + } + + public enum LogReuseWaitStatus : int + { + Nothing = 0, + Checkpoint = 1, + LogBackup = 2, + BackupOrRestore = 3, + Transaction = 4, + Mirroring = 5, + Replication = 6, + SnapshotCreation = 7, + LogScan = 8, + Other = 9 + } + + public enum MirroringSafetyLevel : int + { + None = 0, + Unknown = 1, + Off = 2, + Full = 3 + } + + public enum MirroringStatus : int + { + None = 0, + Suspended = 1, + Disconnected = 2, + Synchronizing = 3, + PendingFailover = 4, + Synchronized = 5 + } + + public enum MirroringWitnessStatus : int + { + None = 0, + Unknown = 1, + Connected = 2, + Disconnected = 3 + } + + [System.Flags] + public enum ReplicationOptions : int + { + None = 0, + Published = 1, + Subscribed = 2, + MergePublished = 4, + MergeSubscribed = 8 + } + + public enum SnapshotIsolationState : int + { + Disabled = 0, + Enabled = 1, + PendingOff = 2, + PendingOn = 3 + } + + [System.Flags] + public enum DatabaseStatus : int + { + Normal = 1, + Restoring = 2, + RecoveryPending = 4, + Recovering = 8, + Suspect = 16, + Offline = 32, + Inaccessible = 62, + Standby = 64, + Shutdown = 128, + EmergencyMode = 256, + AutoClosed = 512 + } + #endregion Public Enums #region Public Classes @@ -585,27 +721,148 @@ public ServerRole( Object server, string name ) { // DSC_SqlDatabasePermission public class Database { + // Boolean Properties + public bool AcceleratedRecoveryEnabled = true; + public bool ActiveDirectory = false; + public bool AnsiNullDefault = true; + public bool AnsiNullsEnabled = true; + public bool AnsiPaddingEnabled = true; + public bool AnsiWarningsEnabled = true; + public bool ArithmeticAbortEnabled = true; public bool AutoClose = false; - public string AvailabilityGroupName = ""; + public bool AutoCreateIncrementalStatisticsEnabled = true; + public bool AutoCreateStatisticsEnabled = true; + public bool AutoShrink = false; + public bool AutoUpdateStatisticsAsync = false; + public bool AutoUpdateStatisticsEnabled = true; + public bool BrokerEnabled = false; + public bool CaseSensitive = false; + public bool ChangeTrackingAutoCleanUp = true; + public bool ChangeTrackingEnabled = false; + public bool CloseCursorsOnCommitEnabled = false; + public bool ConcatenateNullYieldsNull = true; + public bool DatabaseOwnershipChaining = false; + public bool DataRetentionEnabled = false; + public bool DateCorrelationOptimization = false; + public bool DelayedDurability = false; + public bool EncryptionEnabled = false; + public bool HasDatabaseEncryptionKey = false; + public bool HasFileInCloud = false; + public bool HasMemoryOptimizedObjects = false; + public bool HonorBrokerPriority = false; + public bool IsAccessible = true; + public bool IsDatabaseSnapshot = false; + public bool IsDatabaseSnapshotBase = false; + public bool IsDbAccessAdmin = false; + public bool IsDbBackupOperator = false; + public bool IsDbDatareader = false; + public bool IsDbDatawriter = false; + public bool IsDbDdlAdmin = false; + public bool IsDbDenyDatareader = false; + public bool IsDbDenyDatawriter = false; + public bool IsDbManager = false; + public bool IsDbOwner = true; + public bool IsDbSecurityAdmin = false; + public bool IsFabricDatabase = false; + public bool IsFullTextEnabled = false; + public bool IsLedger = false; + public bool IsLoginManager = false; + public bool IsMailHost = false; + public bool IsManagementDataWarehouse = false; + public bool IsMaxSizeApplicable = false; + public bool IsMirroringEnabled = false; + public bool IsParameterizationForced = false; + public bool IsReadCommittedSnapshotOn = false; + public bool IsSqlDw = false; + public bool IsSqlDwEdition = false; + public bool IsSystemObject = false; + public bool IsVarDecimalStorageFormatEnabled = false; + public bool IsVarDecimalStorageFormatSupported = true; + public bool LegacyCardinalityEstimation = false; + public bool LegacyCardinalityEstimationForSecondary = false; + public bool LocalCursorsDefault = false; + public bool NestedTriggersEnabled = true; + public bool NumericRoundAbortEnabled = false; + public bool ParameterSniffing = true; + public bool ParameterSniffingForSecondary = true; + public bool QueryOptimizerHotfixes = false; + public bool QueryOptimizerHotfixesForSecondary = false; + public bool QuotedIdentifiersEnabled = true; + public bool ReadOnly = false; + public bool RecursiveTriggersEnabled = false; + public bool RemoteDataArchiveEnabled = false; + public bool RemoteDataArchiveUseFederatedServiceAccount = false; + public bool TemporalHistoryRetentionEnabled = true; + public bool TransformNoiseWords = false; + public bool Trustworthy = false; + public bool WarnOnRename = true; + + // String Properties + public string AvailabilityGroupName = "TestAG"; + public string AzureServiceObjective = "S1"; + public string CatalogCollation = "SQL_Latin1_General_CP1_CI_AS"; + public string Collation = "SQL_Latin1_General_CP1_CI_AS"; + public string DboLogin = "sa"; + public string DefaultFileGroup = "PRIMARY"; + public string DefaultFileStreamFileGroup = "FileStreamGroup"; + public string DefaultFullTextCatalog = "TestCatalog"; + public string DefaultSchema = "dbo"; + public string FilestreamDirectoryName = "TestDirectory"; + public string MirroringPartner = "TestPartner"; + public string MirroringPartnerInstance = "TestInstance"; + public string MirroringWitness = "TestWitness"; + public string Owner = "sa"; + public string PersistentVersionStoreFileGroup = "PRIMARY"; + public string PrimaryFilePath = "C:\\Data\\"; + public string RemoteDataArchiveCredential = "TestCredential"; + public string RemoteDataArchiveEndpoint = "https://test.endpoint.com"; + public string RemoteDataArchiveLinkedServer = "TestLinkedServer"; + public string RemoteDatabaseName = "RemoteDB"; + public string UserName = "TestUser"; + + // Integer Properties + public int ActiveConnections = 5; + public int ChangeTrackingRetentionPeriod = 2; + public int DefaultFullTextLanguage = 1033; + public int DefaultLanguage = 0; + public int ID = 5; + public int MaxDop = 0; + public int MaxDopForSecondary = 0; + public int MirroringRedoQueueMaxSize = 100; + public int MirroringRoleSequence = 1; + public int MirroringSafetySequence = 1; + public int MirroringTimeout = 10; + public int TargetRecoveryTime = 60; + public int TwoDigitYearCutoff = 2049; + public int Version = 904; + + // Enum Properties + public AvailabilityDatabaseSynchronizationState AvailabilityDatabaseSynchronizationState = AvailabilityDatabaseSynchronizationState.Synchronized; + public RetentionPeriodUnits ChangeTrackingRetentionPeriodUnits = RetentionPeriodUnits.Days; + public CompatibilityLevel CompatibilityLevel = CompatibilityLevel.Version150; + public Microsoft.SqlServer.Management.Common.DatabaseEngineEdition DatabaseEngineEdition = Microsoft.SqlServer.Management.Common.DatabaseEngineEdition.Standard; + public Microsoft.SqlServer.Management.Common.DatabaseEngineType DatabaseEngineType = Microsoft.SqlServer.Management.Common.DatabaseEngineType.Standalone; + public ContainmentType ContainmentType = ContainmentType.None; + public FilestreamNonTransactedAccessType FilestreamNonTransactedAccess = FilestreamNonTransactedAccessType.Off; + public LogReuseWaitStatus LogReuseWaitStatus = LogReuseWaitStatus.Nothing; + public MirroringSafetyLevel MirroringSafetyLevel = MirroringSafetyLevel.Full; + public MirroringStatus MirroringStatus = MirroringStatus.None; + public MirroringWitnessStatus MirroringWitnessStatus = MirroringWitnessStatus.None; + public ReplicationOptions ReplicationOptions = ReplicationOptions.None; + public SnapshotIsolationState SnapshotIsolationState = SnapshotIsolationState.Disabled; + public PageVerify PageVerify = PageVerify.Checksum; + public RecoveryModel RecoveryModel = RecoveryModel.Full; + public DatabaseUserAccess UserAccess = DatabaseUserAccess.Multiple; + public SqlSmoState State = SqlSmoState.Existing; + public DatabaseStatus Status = DatabaseStatus.Normal; + + // Other existing properties public Certificate[] Certificates; - public string ContainmentType = "None"; public DateTime CreateDate; public DatabaseEncryptionKey DatabaseEncryptionKey; - public string DefaultFileStreamFileGroup; - public string DefaultFileGroup = "PRIMARY"; - public string DefaultFullTextCatalog; - public bool EncryptionEnabled = false; - public Hashtable FileGroups; - public string FilestreamDirectoryName; - public string FilestreamNonTransactedAccess = "Off"; - public int ID = 6; - public bool IsMirroringEnabled = false; public DateTime LastBackupDate = DateTime.Now; + public Hashtable FileGroups; public Hashtable LogFiles; - public string Owner = "sa"; - public bool ReadOnly = false; - public string RecoveryModel = "Full"; - public string UserAccess = "Multiple"; public Database( Server server, string name ) { @@ -1958,3 +2215,41 @@ public static Operator CreateTypeInstance() #endregion } + +namespace Microsoft.SqlServer.Management.Common +{ + #region Public Enums + + // TypeName: Microsoft.SqlServer.Management.Common.DatabaseEngineEdition + // BaseType: System.Enum + // Used by: + // Test-SqlDscDatabaseProperty + public enum DatabaseEngineEdition : int + { + Unknown = 0, + Personal = 1, + Standard = 2, + Enterprise = 3, + Express = 4, + SqlDatabase = 5, + SqlDataWarehouse = 6, + SqlStretchDatabase = 7, + SqlManagedInstance = 8, + SqlDatabaseEdge = 9, + SqlAzureArcManagedInstance = 10, + SqlOnDemand = 11 + } + + // TypeName: Microsoft.SqlServer.Management.Common.DatabaseEngineType + // BaseType: System.Enum + // Used by: + // Test-SqlDscDatabaseProperty + public enum DatabaseEngineType : int + { + Unknown = 0, + Standalone = 1, + SqlAzureDatabase = 2, + } + + #endregion +}