PStSQLtTestGenerator.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PStSQLtTestGenerator.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName PStSQLtTestGenerator.Import.DoDotSource -Fallback $false if ($PStSQLtTestGenerator_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName PStSQLtTestGenerator.Import.IndividualFiles -Fallback $false if ($PStSQLtTestGenerator_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code function Invoke-PSTGTestGenerator { <# .SYNOPSIS Create the basic tests for the database project .DESCRIPTION The script will connect to a database on a SQL Server instance, iterate through objects and create tests for the objects. The script will create the following tests - Test if the database settings (i.e. collation) are correct - Test if an object (Function, Procedure, Table, View etc) exists - Test if an object (Function or Procedure) has the correct parameters - Test if an object (Table or View) has the correct columns Each object and each test will be it's own file. .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. This should be the primary replica. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER OutputPath Folder where the files should be written to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder The template folder containing all the templates for the tests. By default it will use the internal templates directory .PARAMETER Function Filter out specific functions that should only be processed .PARAMETER Procedure Filter out specific procedures that should only be processed .PARAMETER Table Filter out specific tables that should only be processed .PARAMETER Index Filter out specific indexes that should be processed .PARAMETER View Filter out specific views that should only be processed .PARAMETER SkipDatabaseTests Skip the database tests .PARAMETER SkipFunctionTests Skip the function tests .PARAMETER SkipProcedureTests Skip the procedure tests .PARAMETER SkipTableTests Skip the table tests .PARAMETER SkipViewTests Skip the view tests .PARAMETER SkipIndexTests Skip the view tests .PARAMETER TestClass Test class name to use for the test .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE PS C:\> Invoke-PSTGTestGenerator -SqlInstance SQLDB1 -Database DB1 -OutputPath c:\projects\DB1\DB1-Tests\TestBasic Iterate through all the objects and output the files to "c:\projects\DB1\DB1-Tests\TestBasic" .EXAMPLE PS C:\> Invoke-PSTGTestGenerator -SqlInstance SQLDB1 -Database DB1 -OutputPath c:\projects\DB1\DB1-Tests\TestBasic -Procedure Proc1, Proc2 Iterate through all the objects but only do "Proc1" and "Proc2" for the procedures. NOTE! All other tests like the table, function and view tests will still be generated .EXAMPLE PS C:\> Invoke-PSTGTestGenerator -SqlInstance SQLDB1 -Database DB1 -OutputPath c:\projects\DB1\DB1-Tests\TestBasic -SkipProcedureTests Iterate through all the objects but do not process the procedures #> [CmdletBinding()] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string[]]$Function, [string[]]$Procedure, [string[]]$Table, [string[]]$Index, [string[]]$View, [switch]$SkipDatabaseTests, [switch]$SkipFunctionTests, [switch]$SkipProcedureTests, [switch]$SkipTableTests, [switch]$SkipIndexTests, [switch]$SkipViewTests, [string]$TestClass, [switch]$EnableException ) begin { # Check the parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter path to output the files to" -Target $OutputPath return } if (-not (Test-Path -Path $OutputPath)) { Stop-PSFFunction -Message "Could not access output path" -Category ResourceUnavailable -Target $OutputPath return } if (-not $Creator) { $Creator = $env:username } if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath return } if (-not $TestClass) { $TestClass = "TestBasic" } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } } process { if (Test-PSFFunctionInterrupt) { return } ######################################################################### # Create the database tests ######################################################################### $totalSteps = 7 $currentStep = 1 $task = "Creating Unit Tests" if (-not $SkipDatabaseTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task try { # Create the collation test New-PSTGDatabaseCollationTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Creator $Creator -TemplateFolder $TemplateFolder -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the database collation test" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the function tests ######################################################################### $currentStep = 2 if (-not $SkipFunctionTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the function existence tests try { New-PSTGObjectExistenceTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Object $Function -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the function existence tests" -Target $Database -ErrorRecord $_ } # Create the function parameter tests try { New-PSTGFunctionParameterTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Function $Function -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the function parameter tests" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the procedure tests ######################################################################### $currentStep = 3 if (-not $SkipProcedureTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the procedure existence tests try { New-PSTGObjectExistenceTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Object $Procedure -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the procedure existence tests" -Target $Database -ErrorRecord $_ } # Create the procedure parameter tests try { New-PSTGProcedureParameterTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Procedure $Procedure -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the procedure parameter tests" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the table tests ######################################################################### $currentStep = 4 if (-not $SkipTableTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the table existence tests try { New-PSTGObjectExistenceTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Object $Table -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the table existence tests" -Target $Database -ErrorRecord $_ } # Create the table column tests try { New-PSTGTableColumnTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Table $Table -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the table column tests" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the table index tests ######################################################################### $currentStep = 5 if (-not $SkipIndexTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the view existence tests try { New-PSTGTableIndexTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Table $Table -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the view existence tests" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the index tests ######################################################################### $currentStep = 6 if (-not $SkipIndexTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the view existence tests try { New-PSTGIndexColumnTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Table $Table -Index $Index -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the view existence tests" -Target $Database -ErrorRecord $_ } } ######################################################################### # Create the view tests ######################################################################### $currentStep = 7 if (-not $SkipViewTests) { Write-Progress -Id 1 -Activity "Creating tSQLt Unit Tests" -Status 'Progress->' -PercentComplete $($currentStep / $totalSteps * 100) -CurrentOperation $task # Create the view existence tests try { New-PSTGObjectExistenceTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -Object $View -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the view existence tests" -Target $Database -ErrorRecord $_ } # Create the view column tests try { New-PSTGViewColumnTest -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -View $View -Creator $Creator -OutputPath $OutputPath -TestClass $TestClass -EnableException } catch { Stop-PSFFunction -Message "Something went wrong creating the table column tests" -Target $Database -ErrorRecord $_ } } } } function New-PSTGDatabaseCollationTest { <# .SYNOPSIS Function to create a collation test .DESCRIPTION The function will lookup the current collation of the database and create a test with that value .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGDatabaseCollationTest -Database DB1 -OutputPath "C:\Projects\DB1\TestBasic\" Create a new database collation test #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [Parameter(Mandatory)][string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } } process { if (Test-PSFFunctionInterrupt) { return } $testName = "test If database has correct collation" $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "DatabaseCollationTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'DatabaseCollationTest.template'" -Target $testName -ErrorRecord $_ } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___DATABASE___", $Database) $script = $script.Replace("___COLLATION___", $server.Databases[$Database].Collation) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) # Write the test if ($PSCmdlet.ShouldProcess("$Database", "Writing Database Collation Test")) { try { Write-PSFMessage -Message "Creating collation test for '$Database'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "DatabaseCollation" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } function New-PSTGFunctionParameterTest { <# .SYNOPSIS Function to create parameter tests .DESCRIPTION The function will retrieve the current parameters for a function and create a test for it .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Function Function(s) to create tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Function object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGFunctionParameterTest -Function $function -OutputPath $OutputPath Create a new function parameter test .EXAMPLE $functions | New-PSTGFunctionParameterTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Function, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Function Parameters" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $Function -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a Function" return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].UserDefinedFunctions | Where-Object Name -in $InputObject | Select-Object Schema, Name, Parameters } else { $objects += $server.Databases[$Database].UserDefinedFunctions | Where-Object IsSystemObject -eq $false | Select-Object Schema, Name, Parameters } if ($Function) { $objects = $objects | Where-Object Name -in $Function } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating function test $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If function $($input.Schema).$($input.Name) has the correct parameters" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" # Get the parameters $parameters = $input.Parameters if ($parameters.Count -ge 1) { # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "FunctionParameterTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'FunctionParameterTest.template'" -Target $testName -ErrorRecord $_ } $paramTextCollection = @() # Loop through the parameters foreach ($parameter in $parameters) { $paramText = "`t('$($parameter.Name)', '$($parameter.DataType.Name)', $($parameter.DataType.MaximumLength), $($parameter.DataType.NumericPrecision), $($parameter.DataType.NumericScale))" $paramTextCollection += $paramText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___PARAMETERS___", ($paramTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Function Parameter Test")) { try { Write-PSFMessage -Message "Creating function parameter test for function '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "FunctionParameter" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } else { Write-PSFMessage -Message "Function $($input.Schema).$($input.Name) does not have any parameters. Skipping..." } $functionStep++ } } } } function New-PSTGIndexColumnTest { <# .SYNOPSIS Function to test the columns for an index .DESCRIPTION The function will retrieve the current columns for an index and create a test for it .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Table Table(s) to create tests for .PARAMETER Index Index(es) to create tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Table object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGIndexColumnTest -Table $table -OutputPath $OutputPath Create a new index column test .EXAMPLE $tables | New-PSTGIndexColumnTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Table, [string[]]$Index, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database '$Database' cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Index Columns" -Status 'Progress->' -CurrentOperation $task -Id 2 $tables = @() if ($Table) { $tables += $server.Databases[$Database].Tables | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $Table } | Select-Object Schema, Name, Indexes } else { $tables += $server.Databases[$Database].Tables | Where-Object { $_.IsSystemObject -eq $false } | Select-Object Schema, Name, Indexes } } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $Table -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a Table" return } $objects = @() if ($InputObject) { $objects += $tables.Indexes | Where-Object Name -in $InputObject | Select-Object Name, IndexedColumns } else { $objects += $tables.Indexes | Select-Object Name, IndexedColumns } if ($Index) { $objects = $objects | Where-Object Name -in $Index } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating index $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If index $($input.Name) has the correct columns" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern $creator = $env:username # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "IndexColumnTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'IndexColumnTest.template'" -Target $testName -ErrorRecord $_ } # Get the columns $columns = $input.IndexedColumns $columnTextCollection = @() # Loop through the columns foreach ($column in $columns) { $columnText = "`t('$($column.Name)')" $columnTextCollection += $columnText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___COLUMNS___", ($columnTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Index Column Test")) { try { Write-PSFMessage -Message "Creating index column test for index '$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "IndexColumn" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } } } function New-PSTGObjectExistenceTest { <# .SYNOPSIS Function to check if an object exists .DESCRIPTION The function will create a test to check for the existence of an object .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Object The object(s) to create the tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Login object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGObjectExistenceTest -Object $object -OutputPath $OutputPath Create a new object existence test .EXAMPLE $objects | New-PSTGObjectExistenceTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Object, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } if (-not $TestClass) { $TestClass = "TestBasic" } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Object Existence" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].Tables | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "Table" } } | Where-Object Name -in $InputObject $objects += $server.Databases[$Database].StoredProcedures | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "StoredProcedure" } }, IsSystemObject | Where-Object { $_.Name -in $InputObject -and $_.IsSystemObject -eq $false } $objects += $server.Databases[$Database].UserDefinedFunctions | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "UserDefinedFunction" } }, IsSystemObject | Where-Object { $_.Name -in $InputObject -and $_.IsSystemObject -eq $false } $objects += $server.Databases[$Database].Views | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "View" } }, IsSystemObject | Where-Object { $_.Name -in $InputObject -and $_.IsSystemObject -eq $false } } else { $objects += $server.Databases[$Database].Tables | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "Table" } } $objects += $server.Databases[$Database].StoredProcedures | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "StoredProcedure" } }, IsSystemObject | Where-Object { $_.IsSystemObject -eq $false } $objects += $server.Databases[$Database].UserDefinedFunctions | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "UserDefinedFunction" } }, IsSystemObject | Where-Object { $_.IsSystemObject -eq $false } $objects += $server.Databases[$Database].Views | Select-Object Schema, Name, @{Name = "ObjectType"; Expression = { "View" } }, IsSystemObject | Where-Object { $_.IsSystemObject -eq $false } } if ($Object) { $objects = $objects | Where-Object Name -in $Object } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating object existence test $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 switch ($input.ObjectType) { "StoredProcedure" { $objectType = "stored procedure" } "Table" { $objectType = "table" } "UserDefinedFunction" { $objectType = "user defined function" } "View" { $objectType = "View" } } $testName = "test If $($objectType.ToLower()) $($input.Schema)`.$($input.Name) exists" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "ObjectExistence.template") } catch { Stop-PSFFunction -Message "Could not import test template 'ObjectExistence.template'" -Target $testName -ErrorRecord $_ } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___OBJECTTYPE___", $objectType.ToLower()) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Object Existence Test")) { try { Write-PSFMessage -Message "Creating existence test for $($objectType.ToLower()) '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "ObjectExistence" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } } } function New-PSTGProcedureParameterTest { <# .SYNOPSIS Function to create procedure tests .DESCRIPTION The function will collect the parameter(s) of the procedure(s) and create the test .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Procedure Procedure(s) to create tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Procedure object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGProcedureParameterTest -Procedure $procedure -OutputPath $OutputPath Create a new procedure parameter test .EXAMPLE $procedures | New-PSTGProcedureParameterTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Procedure, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { Stop-PSFFunction -Message "Could not access output path" -Category ResourceUnavailable -Target $OutputPath } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Stored Procedure Parameters" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $Procedure -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a Procedure" return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].StoredProcedures | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $InputObject } | Select-Object Schema, Name, Parameters } else { $objects += $server.Databases[$Database].StoredProcedures | Where-Object IsSystemObject -eq $false | Select-Object Schema, Name, Parameters } if ($Procedure) { $objects = $objects | Where-Object Name -in $Procedure } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating procedure $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If stored procedure $($input.Schema).$($input.Name) has the correct parameters" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern $creator = $env:username # Get the parameters $parameters = $input.Parameters if ($parameters.Count -ge 1) { # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "ProcedureParameterTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'ProcedureParameterTest.template'" -Target $testName -ErrorRecord $_ } $paramTextCollection = @() # Loop through the parameters foreach ($parameter in $parameters) { $paramText = "`t('$($parameter.Name)', '$($parameter.DataType.Name)', $($parameter.DataType.MaximumLength), $($parameter.DataType.NumericPrecision), $($parameter.DataType.NumericScale))" $paramTextCollection += $paramText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___PARAMETERS___", ($paramTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Procedure Parameter Test")) { try { Write-PSFMessage -Message "Creating procedure parameter test for procedure '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "ProcedureParameter" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } else { Write-PSFMessage -Message "Procedure $($input.Schema).$($input.Name) does not have any parameters. Skipping..." } } } } } function New-PSTGTableColumnTest { <# .SYNOPSIS Function to test thee columns for a table .DESCRIPTION The function will retrieve the current columns for a table and create a test for it .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Table Table(s) to create tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Table object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGTableColumnTest -Table $table -OutputPath $OutputPath Create a new table column test .EXAMPLE $tables | New-PSTGTableColumnTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Table, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database '$Database' cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Table Columns" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $Table -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a Table" return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].Tables | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $InputObject } | Select-Object Schema, Name, Columns } else { $objects += $server.Databases[$Database].Tables | Select-Object Schema, Name, Columns } if ($Table) { $objects = $objects | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $Table } } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating table $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If table $($input.Schema).$($input.Name) has the correct columns" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern $creator = $env:username # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "TableColumnTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'TableColumnTest.template'" -Target $testName -ErrorRecord $_ } # Get the columns $columns = $input.Columns $columnTextCollection = @() # Loop through the columns foreach ($column in $columns) { $columnText = "`t('$($column.Name)', '$($column.DataType.Name)', $($column.DataType.MaximumLength), $($column.DataType.NumericPrecision), $($column.DataType.NumericScale))" $columnTextCollection += $columnText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___COLUMNS___", ($columnTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Table Column Test")) { try { Write-PSFMessage -Message "Creating table column test for table '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "TableColumn" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } } } function New-PSTGTableIndexTest { <# .SYNOPSIS Function to test the indexes for a table .DESCRIPTION The function will retrieve the current indexes for a table and create a test for it .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER Table Table(s) to create tests for .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a Table object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGTableIndexTest -Table $table -OutputPath $OutputPath Create a new table column test .EXAMPLE $tables | New-PSTGTableIndexTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$Table, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database '$Database' cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " Table Columns" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $Table -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a Table" return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].Tables | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $InputObject } | Select-Object Schema, Name, Indexes } else { $objects += $server.Databases[$Database].Tables | Select-Object Schema, Name, Indexes } if ($Table) { $objects = $objects | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -in $Table } } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { if ($input.Indexes.Count -ge 1) { $task = "Creating index $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If table $($input.Schema).$($input.Name) has the correct indexes" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern $creator = $env:username # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "TableIndexTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'TableIndexTest.template'" -Target $testName -ErrorRecord $_ } # Get the columns $indexes = $input.Indexes $indexTextCollection = @() # Loop through the columns foreach ($index in $indexes) { $indexText = "`t('$($index.Name)')" $indexTextCollection += $indexText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___INDEXES___", ($indexTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing Table Index Test")) { try { Write-PSFMessage -Message "Creating table index test for table '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "TableIndex" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } } } } function New-PSTGViewColumnTest { <# .SYNOPSIS Function to create view column tests .DESCRIPTION The function will retrieve the columns for a view and create a test for it .PARAMETER SqlInstance The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher. .PARAMETER SqlCredential Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential). Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported. For MFA support, please use Connect-DbaInstance. .PARAMETER Database The database or databases to add. .PARAMETER View View(s) to create tests forr .PARAMETER OutputPath Path to output the test to .PARAMETER Creator The person that created the tests. By default the command will get the environment username .PARAMETER TemplateFolder Path to template folder. By default the internal templates folder will be used .PARAMETER TestClass Test class name to use for the test .PARAMETER InputObject Takes the parameters required from a View object that has been piped into the command .PARAMETER WhatIf Shows what would happen if the command were to run. No actions are actually performed. .PARAMETER Confirm Prompts you for confirmation before executing any changing operations within the command. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .EXAMPLE New-PSTGViewColumnTest -View $view -OutputPath $OutputPath Create a new view column test .EXAMPLE $views | New-PSTGViewColumnTest -OutputPath $OutputPath Create the tests using pipelines #> [CmdletBinding(SupportsShouldProcess)] param( [DbaInstanceParameter]$SqlInstance, [pscredential]$SqlCredential, [string]$Database, [string[]]$View, [string]$OutputPath, [string]$Creator, [string]$TemplateFolder, [string]$TestClass, [parameter(ParameterSetName = "InputObject", ValueFromPipeline)] [object[]]$InputObject, [switch]$EnableException ) begin { # Check parameters if (-not $SqlInstance) { Stop-PSFFunction -Message "Please enter a SQL Server instance" -Target $SqlInstance return } if (-not $Database) { Stop-PSFFunction -Message "Please enter a database" -Target $Database return } # Check the output path if (-not $OutputPath) { Stop-PSFFunction -Message "Please enter an output path" return } if (-not (Test-Path -Path $OutputPath)) { try { $null = New-Item -Path $OutputPath -ItemType Directory } catch { Stop-PSFFunction -Message "Something went wrong creating the output directory" -Target $OutputPath -ErrorRecord $_ } } # Check the template folder if (-not $TemplateFolder) { $TemplateFolder = Join-Path -Path ($script:ModuleRoot) -ChildPath "internal\templates" } if (-not (Test-Path -Path $TemplateFolder)) { Stop-PSFFunction -Message "Could not find template folder" -Target $OutputPath } if (-not $TestClass) { $TestClass = "TestBasic" } $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern if (-not $Creator) { $Creator = $env:username } # Connect to the server try { $server = Connect-DbaInstance -SqlInstance $Sqlinstance -SqlCredential $SqlCredential } catch { Stop-PSFFunction -Message "Could not connect to '$Sqlinstance'" -Target $Sqlinstance -ErrorRecord $_ -Category ConnectionError return } # Check if the database exists if ($Database -notin $server.Databases.Name) { Stop-PSFFunction -Message "Database cannot be found on '$SqlInstance'" -Target $Database } $task = "Collecting objects" Write-Progress -ParentId 1 -Activity " View Columns" -Status 'Progress->' -CurrentOperation $task -Id 2 } process { if (Test-PSFFunctionInterrupt) { return } if (-not $InputObject -and -not $View -and -not $SqlInstance) { Stop-PSFFunction -Message "You must pipe in an object or specify a View" return } $objects = @() if ($InputObject) { $objects += $server.Databases[$Database].Views | Where-Object Name -in $InputObject | Select-Object Schema, Name, Columns } else { $objects += $server.Databases[$Database].Views | Where-Object IsSystemObject -eq $false | Select-Object Schema, Name, Columns } if ($View) { $objects = $objects | Where-Object Name -in $View } $objectCount = $objects.Count $objectStep = 1 if ($objectCount -ge 1) { foreach ($input in $objects) { $task = "Creating view $($objectStep) of $($objectCount)" Write-Progress -ParentId 1 -Activity "Creating..." -Status 'Progress->' -PercentComplete ($objectStep / $objectCount * 100) -CurrentOperation $task -Id 2 $testName = "test If view $($input.Schema).$($input.Name) has the correct columns" # Test if the name of the test does not become too long if ($testName.Length -gt 128) { Stop-PSFFunction -Message "Name of the test is too long" -Target $testName } $fileName = Join-Path -Path $OutputPath -ChildPath "$($testName).sql" $date = Get-Date -Format (Get-culture).DateTimeFormat.ShortDatePattern $creator = $env:username # Import the template try { $script = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath "ViewColumnTest.template") } catch { Stop-PSFFunction -Message "Could not import test template 'ViewColumnTest.template'" -Target $testName -ErrorRecord $_ } # Get the columns $columns = $input.Columns $columnTextCollection = @() # Loop through the columns foreach ($column in $columns) { $columnText = "`t('$($column.Name)', '$($column.DataType.Name)', $($column.DataType.MaximumLength), $($column.DataType.NumericPrecision), $($parameter.DataType.NumericScale))" $columnTextCollection += $columnText } # Replace the markers with the content $script = $script.Replace("___TESTCLASS___", $TestClass) $script = $script.Replace("___TESTNAME___", $testName) $script = $script.Replace("___SCHEMA___", $input.Schema) $script = $script.Replace("___NAME___", $input.Name) $script = $script.Replace("___CREATOR___", $creator) $script = $script.Replace("___DATE___", $date) $script = $script.Replace("___COLUMNS___", ($columnTextCollection -join ",`n") + ";") # Write the test if ($PSCmdlet.ShouldProcess("$($input.Schema).$($input.Name)", "Writing View Column Test")) { try { Write-PSFMessage -Message "Creating view column test for table '$($input.Schema).$($input.Name)'" $script | Out-File -FilePath $fileName [PSCustomObject]@{ TestName = $testName Category = "ViewColumn" Creator = $creator FileName = $fileName } } catch { Stop-PSFFunction -Message "Something went wrong writing the test" -Target $testName -ErrorRecord $_ } } } } } } <# This is an example configuration file By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> <# # Example Configuration Set-PSFConfig -Module 'PStSQLtTestGenerator' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" #> Set-PSFConfig -Module 'PStSQLtTestGenerator' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'PStSQLtTestGenerator' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." <# # Example: Register-PSFTeppScriptblock -Name "PStSQLtTestGenerator.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } #> <# # Example: Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name PStSQLtTestGenerator.alcohol #> New-PSFLicense -Product 'PStSQLtTestGenerator' -Manufacturer 'sstad' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2019-09-18") -Text @" Copyright (c) 2019 sstad Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |