functions/crm-ci.Tests.ps1

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"

if (-not $Global:cred) {
  $Global:cred = Get-Credential -Message "CRM Credentials" -UserName $env:USERNAME 
}

Function Assert-PropertiesMatch {
  Param (
    $Expected,
    $Actual
  )
  Process {
    foreach ($prop in ($Expected.psobject.properties.name)) {

      if ($Expected."$prop".GetType().Name -eq "DateTime") {
        [string]$Actual."$prop".ToString() | Should -Be $Expected."$prop".ToString()
      }
      else {
        [string]$Actual."$prop" | Should -Be $Expected."$prop"
      }
    }
  }
}

Describe "Get-CrmConnectionString" {
  It "Returns either a connection string or just the url depending on the -UrlOnly switch" {
    [string]$connString = Get-CrmConnectionString -CrmInstance CRMADVISE  
    $connString.StartsWith("AuthType=AD;Url=") | Should -Be $true
      
    [string]$urlOnly = Get-CrmConnectionString -CrmInstance CRMADVISE -UrlOnly
    $urlOnly.StartsWith("https://") | Should -Be $true
  }
  It "Returns the correct address based on the crm instance" {
    [string]$connString = Get-CrmConnectionString -CrmInstance CRMADVISETEST
    $connString | Should -Be "AuthType=AD;Url=https://advisedevcrm.regent.edu/CRMADVISETEST;"
  }
}

Describe "Get-RegentConnection" {
  It "should call through to Get-CrmConnection" {
    $regent = Get-RegentConnection -CrmInstance CRMRECRUITTEST 
    $datatTools = (Get-CrmConnection -ConnectionString "AuthType=AD;Url=https://rctrdevcrm.regent.edu/CRMRECRUITTEST;")
    $regent.GetType() | Should -Be $datatTools.GetType()
  }
}

Describe "Export-RegentSolution" {
  Context "Given a config file specifying repository path TestDrive:/solutions" {
    New-Item TestDrive:\solutions -ItemType Directory
    Mock 'Get-Config' {
      [PSCustomObject]@{
        RepositoryPath = "TestDrive:\solutions";
        DefaultBranch  = "master";
      }
    }
    It "should download the solution as both managed and unmanged, and export it to the path specified in the config" {
      $conn = Get-RegentConnection -CrmInstance CRMRECRUITTEST
      $results = Export-RegentSolution -SolutionName "RecordLocked" -conn $conn -Verbose
      $results[0].ExportSolutionResponse.GetType().Name | Should -Be "ExportSolutionResponse"
      $results[1].ExportSolutionResponse.GetType().Name | Should -Be "ExportSolutionResponse"
      Test-Path "TestDrive:/solutions/RecordLocked/" | Should -Be $true
    }
  }
}
Describe "Get-SolutionImportLog" {
  Context "Given an ImportJob existing in CRM CRMRECRUITTEST with an ID of 60255876-6aae-e511-80c2-0050569b65f8" {
    It "Should return the formatted xml results and export them if specified" {
      $exportPath = "TestDrive:\logs\log.xml"
      # $exportPath = "C:\repos\ps\crm-ci\log.xml"
      $id = "60255876-6aae-e511-80c2-0050569b65f8"
      # $id = "34412c0d-ac41-49ed-a1db-ca8ae5450e1f"
      $results = Get-SolutionImportLog `
        -CrmInstance CRMRECRUITTEST `
        -ImportJobId $id `
        -ExportPath $exportPath `
        -Credential $global:cred
      
      $results | Should -Not -BeNullOrEmpty
      
      Test-Path $exportPath | Should -Be $true
      $contents = Get-Content $exportPath
      $contents | Should -Be $results[1]
    }
  }
}

Describe "Import-RegentSolution" {
  Context "Given a config file specifying repository path TestDrive:/solutions" {
    New-Item TestDrive:\solutions -ItemType Directory
    Mock 'Get-Config' {
      [PSCustomObject]@{
        RepositoryPath = "C:\repos\ps\crm-ci\test-data\unpacked";
        DefaultBranch  = "master";
      }
    }

    It "Should Import the solution by default" {
      Import-RegentSolution `
        -SolutionName RecordLocked `
        -CrmInstance CRMRECRUITTEST `
        -Emails "dhines@regent.edu" `
        -Managed $false `
        -Credential $global:cred `
        -Verbose
    }
    It "Should throw any import errors and email the error message" {
      # In this example, I'm importing a managed version of solution into a CRM that
      # has the unmanged version intalled, which will cause an import error
      {
        Import-RegentSolution `
          -SolutionName RecordLocked `
          -CrmInstance CRMRECRUITTEST `
          -Managed $true `
          -Emails "dhines@regent.edu" `
          -Credential $global:cred `
          -Verbose 
      } | Should -Throw
    }
  }
}

Describe "Invoke-RegentScheduledImports" {
  Mock -CommandName Import-RegentSolution -Verifiable -MockWith {return}
  New-Item TestDrive:\solutions -ItemType Directory
  Mock 'Get-Config' {
    [PSCustomObject]@{
      RepositoryPath = "TestDrive:\solutions";
      DefaultBranch  = "master";
    }
  }
  $configPath = "TestDrive:\solutions\solution-imports.json"
  
  Context "Given no import jobs" {
    if (Test-Path $configPath) {
      Remove-Item $configPath
    }
    # On reconsideration, I don't think it should throw an error, since it will be run all the time by the task scheduler, whether or not a config file has been created
    # It "Should throw an error if the config file doesn't exist" {
    # { Invoke-RegentScheduledImports } | Should -Throw
    # }

    It "Should do nothing if there are no scheduled jobs" {
      "" > $configPath
      { Invoke-RegentScheduledImports } | Should -Not -Throw
    }
  }
  Context "Given only one import job that's due on runtime" {
    BeforeEach {
      $time = (Get-Date).ToUniversalTime()
      $testConfig = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = $time;
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      $testConfig | ConvertTo-Json > $configPath
    }
    It "Should call Import-RegentSolution, then remove it" {
      Invoke-RegentScheduledImports
      Assert-MockCalled -CommandName "Import-RegentSolution" -Scope It -Exactly 1 `
        -ParameterFilter {
        $SolutionName -eq $testConfig.SolutionName `
          -and $CrmInstance -eq $testConfig.CrmInstance `
          -and $Managed -eq $testConfig.Managed `
          -and $PublishCustomizations -eq $testConfig.PublishCustomizations `
          -and $Emails -eq $testConfig.Emails `
          -and $Credential.UserName -eq $testConfig.UserName
      }
      $results = Get-Content $configPath | ConvertFrom-Json
      $results | Should -Be $null
    }
  }  
  Context "Given only one import job that's not due yet" {
    BeforeEach {
      $time = (Get-Date).AddHours(1).ToUniversalTime()
      $testConfig = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = $time;
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      $testConfig | ConvertTo-Json > $configPath
    }
    It "Should skip the job if it's not due" {
      Invoke-RegentScheduledImports
      Assert-MockCalled -CommandName "Import-RegentSolution" -Scope It -Exactly 0
      $results = Get-Content $configPath | ConvertFrom-Json `
        | Select-Object -Property * -ExcludeProperty Password
      $expected = $testConfig | Select-Object -Property * -ExcludeProperty Password
      Assert-PropertiesMatch -Expected $expected -Actual $results
    }
  }
  Context "Given multiple import jobs" {
    BeforeEach {
      $time1 = (Get-Date).ToUniversalTime()
      $time2 = (Get-Date).AddHours(2).ToUniversalTime()
      $time3 = (Get-date).AddHours(-1).ToUniversalTime()

      # Configs 1 and two are identical except for the time,
      # so asserting Import-RegentSolution is only called once will test the filtering capabilitiy
      $config1 = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = $time1;
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      $config2 = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = $time2;
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      $config3 = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMADVISE";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "FooSolution";
        Emails                = "bam@baz.com";
        Time                  = $time3;
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      @($config1, $config2, $config3) | ConvertTo-Json > $configPath
    }
    It "Should call Import-RegentSolution on each job that is due" {
      Invoke-RegentScheduledImports
      Assert-MockCalled -CommandName "Import-RegentSolution" -Scope It -Exactly 1 `
        -ParameterFilter {
        $SolutionName -eq $config1.SolutionName `
          -and $CrmInstance -eq $config1.CrmInstance `
          -and $Managed -eq $config1.Managed `
          -and $PublishCustomizations -eq $config1.PublishCustomizations `
          -and $Emails -eq $config1.Emails `
          -and $Credential.UserName -eq $config1.UserName
      }
      Assert-MockCalled -CommandName "Import-RegentSolution" -Scope It -Exactly 1 `
        -ParameterFilter {
        $SolutionName -eq $config3.SolutionName `
          -and $CrmInstance -eq $config3.CrmInstance `
          -and $Managed -eq $config3.Managed `
          -and $PublishCustomizations -eq $config3.PublishCustomizations `
          -and $Emails -eq $config3.Emails `
          -and $Credential.UserName -eq $config3.UserName
      }
    }
    It "Should remove a job from the config after a successful import" {
      Invoke-RegentScheduledImports

      $results = Get-Content $configPath | ConvertFrom-Json `
        | Select-Object -Property * -ExcludeProperty Password
      $expected = $config2 | Select-Object -Property * -ExcludeProperty Password
      Assert-PropertiesMatch -Expected $expected -Actual $results
    }
  }
}

Describe "Move-RegentSolution" {
  Mock -CommandName Import-RegentSolution -Verifiable -MockWith {"Hello World!" > success.txt}
  Mock -CommandName Export-RegentSolution -Verifiable -MockWith {return}
  Mock -CommandName Push-RegentSolutionChanges -Verifiable -MockWith {return}
  $configPath = "$env:APPDATA\solution-imports.json"
  Context "Given a config file with a repository path TestDrive:/" { 
    BeforeEach {
      if (Test-Path $configPath) {
        Remove-Item $configPath -Force
      }
    }
    It "Should export the specified solution, unpack it, and push the changes" {
      Move-RegentSolution `
        -Credential $global:cred `
        -SolutionName "RegentUniversity" `
        -SourceCrmInstance CRMRECRUIT `
        -DestinationCrmInstance CRMRECRUIT `
        -Managed $true `
        -PublishCustomizations $true `
        -Emails "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu" `
        -UpdateVersion "2.5" `
        -CommitMessage "Initial Commit" `
        -Branch "mybranch" `
        -ImportTime (Get-Date).AddSeconds(3) `
        -Force

      Assert-MockCalled -Scope It -CommandName Export-RegentSolution `
        -Exactly 1 `
        -ParameterFilter { 
        $SolutionName -eq "RegentUniversity" `
          -and $UpdateVersion -eq "2.5"
      }
      Assert-MockCalled -Scope It -CommandName Push-RegentSolutionChanges `
        -Exactly 1 `
        -ParameterFilter { 
        $CommitMessage -eq "Initial Commit" `
          -and $Branch -eq "mybranch"
      }
    }
    It "Should import the solution immediately if no time is specified" {
      Move-RegentSolution `
        -Credential $global:cred `
        -SolutionName "RegentUniversity" `
        -SourceCrmInstance CRMRECRUIT `
        -DestinationCrmInstance CRMRECRUIT `
        -Managed $true `
        -PublishCustomizations $true `
        -Emails "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu" `
        -UpdateVersion "2.5" `
        -CommitMessage "Initial Commit" `
        -Branch "mybranch" `
        -Force
      
      Assert-MockCalled -Scope It -CommandName Import-RegentSolution `
        -Exactly 1 `
        -ParameterFilter {
        $SolutionName -eq "RegentUniversity" `
          -and $CrmInstance -eq "CRMRECRUIT" `
          -and $Managed -eq $true `
          -and $PublishCustomizations -eq $true `
          -and $Emails -eq "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu" `
          -and $Credential -eq $global:cred
      }
    }

    It "Should schedule the import job for the specified time by adding it to the config" {
      $time = (Get-Date).AddHours(1).ToUniversalTime()

      $expectedConfig = [pscustomobject] @{ 
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = $time;
      }
      Move-RegentSolution `
        -Credential $global:cred `
        -SolutionName $expectedConfig.SolutionName `
        -SourceCrmInstance CRMRECRUIT `
        -DestinationCrmInstance $expectedConfig.CrmInstance `
        -Managed $expectedConfig.Managed `
        -PublishCustomizations $expectedConfig.PublishCustomizations `
        -Emails $expectedConfig.Emails `
        -CommitMessage "Initial Commit" `
        -Branch "mybranch" `
        -ImportTime  $time `
        -Force
      
      Test-Path $configPath | Should -Be $true
      #Grab the config, but exclude the password
      $config = (Get-Content $configPath) | ConvertFrom-Json | Select-Object -Property "*" -ExcludeProperty "Password"

      Assert-PropertiesMatch -Expected $expectedConfig -Actual $config
    }

    It "Should throw an error if the specified solution isn't found" {
      {Move-RegentSolution `
          -Credential $global:cred `
          -SolutionName "NonExistingSolution" `
          -SourceCrmInstance CRMRECRUIT `
          -DestinationCrmInstance CRMRECRUIT `
          -Managed $true `
          -PublishCustomizations $true `
          -Emails "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu" `
          -UpdateVersion "2.5" `
          -CommitMessage "Initial Commit" `
          -Branch "mybranch" `
          -ImportTime (Get-Date).AddSeconds(5) `
          -Force} | Should -Throw
    }

    It "Should handle existing jobs by appending the new job to the existing file" {
      $preExistingConfig = [pscustomobject] @{
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RegentUniversity";
        Emails                = "dhines@regent.edu;bryatho@regent.edu;acofer@regent.edu";
        Time                  = "13:00"
        Password              = $global:cred.Password | ConvertFrom-SecureString
      }
      $preExistingConfig | ConvertTo-Json > $configPath

      $time = (Get-Date).AddHours(1).ToUniversalTime()
      $expectedConfig = [pscustomobject] @{
        UserName              = $env:USERNAME;
        CrmInstance           = "CRMRECRUIT";
        PublishCustomizations = $true;
        Managed               = $true;
        SolutionName          = "RecordLocked";
        Emails                = "dhines@regent.edu;";
        Time                  = $time;
      }
  
      Move-RegentSolution `
        -Credential $global:cred `
        -SolutionName $expectedConfig.SolutionName`
        -SourceCrmInstance CRMRECRUIT `
        -DestinationCrmInstance $expectedConfig.CrmInstance `
        -Managed $expectedConfig.Managed `
        -PublishCustomizations $expectedConfig.PublishCustomizations `
        -Emails $expectedConfig.Emails `
        -CommitMessage "Initial Commit" `
        -Branch "mybranch" `
        -ImportTime  $time `
        -Force

      $newConfig = Get-Content $configPath `
        | ConvertFrom-Json | Select-Object -Property "*" -ExcludeProperty "Password"

      Assert-PropertiesMatch -Expected $preExistingConfig -Actual $newConfig.SyncRoot[0]
      Assert-PropertiesMatch -Expected $expectedConfig -Actual $newConfig.SyncRoot[1]
    }
  }
}