Functions/Import-BT_Module.Tests.ps1

describe "BitTitan.Runbooks.Modules/Import-BT_Module" -Tag "module", "unit" {

    # Import the function to test
    . "$($PSScriptRoot)\Import-BT_Module.ps1"

    # Declare external functions and mocks
    function Get-Module {
        param ($Name, [Switch]$ListAvailable)
    }
    function Install-Module {
        param ($Name)
    }
    function Find-Module {
        param ($Name)
    }
    function Get-ChildItem {}

    # Retrieve the current BitTitan Runbook Environment settings
    $previousPSDefaultParameterValues = $Global:PSDefaultParameterValues

    context "when running on the Prod local machine environment" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-Module
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }

        # Set up the settings
        $Global:PSDefaultParameterValues["*-BT_*:Environment"] = "BT"
        $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $true

        it "imports the module" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction SilentlyContinue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Install-Module -Times 0 -Exactly -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }
    }

    context "when running on the Beta local machine environment" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-Module
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }

        # Set up the settings
        $Global:PSDefaultParameterValues["*-BT_*:Environment"] = "Beta"
        $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $true

        it "imports the module" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction SilentlyContinue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Install-Module -Times 0 -Exactly -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }
    }

    context "when running on the Prod MSPComplete environment" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-ChildItem
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return
            }
            return [PSCustomObject]@{
                Name = "0.1.0"
            }
        }

        # Mock Get-Module
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }

        # Set up the settings
        $Global:PSDefaultParameterValues["*-BT_*:Environment"] = "BT"
        $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $false

        it "installs and imports the module if the module is currently not installed" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction continue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Install-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }

        # Declare mocks
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    FullName = "BitTitan.Runbooks.TestModule"
                }
            }
            elseif ($Script:numberTimesGetChildItemCalled -eq 1) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    Name = "0.1.0"
                }
            }
            return [PSCustomObject]@{
                Name = "0.2.0"
            }
        }
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.2.0"
            }
        }
        mock Find-Module {
            return [PSCustomObject]@{
                Version = [Version]"0.2.0"
            }
        }

        it "installs and imports the module if the module currently installed is not the latest version" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction Continue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Find-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Install-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }

        # Declare mocks
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    FullName = "BitTitan.Runbooks.TestModule"
                }
            }
            elseif ($Script:numberTimesGetChildItemCalled -eq 1) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    Name = "0.1.0"
                }
            }
            return [PSCustomObject]@{
                Name = "0.1.0"
            }
        }
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }
        mock Find-Module {
            return [PSCustomObject]@{
                Version = [Version]"0.1.0"
            }
        }

        it "only imports the module if the module currently installed is the latest version" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction Continue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Find-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Install-Module -Times 0 -Exactly -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }
    }

    context "when running on the Beta MSPComplete environment" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-ChildItem
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return
            }
            return [PSCustomObject]@{
                Name = "0.1.0"
            }
        }

        # Mock Get-Module
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }

        # Mock Get-Item
        mock Get-Item {
            return [PSCustomObject]@{
                FullName = "FullName"
            }
        }

        # Mock Move-Item
        mock Move-Item {}

        # Set up the settings
        $Global:PSDefaultParameterValues["*-BT_*:Environment"] = "Beta"
        $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $false

        it "installs and imports the module if the module is currently not installed" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction SilentlyContinue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Install-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule.Beta"
            } -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }

        # Declare mocks
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    FullName = "BitTitan.Runbooks.TestModule"
                }
            }
            elseif ($Script:numberTimesGetChildItemCalled -eq 1) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    Name = "0.1.0"
                }
            }
            return [PSCustomObject]@{
                Name = "0.2.0"
            }
        }
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.2.0"
            }
        }
        mock Find-Module {
            return [PSCustomObject]@{
                Version = [Version]"0.2.0"
            }
        }

        it "installs and imports the module if the module currently installed is not the latest version" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction Continue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Find-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Install-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule.Beta"
            } -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }

        # Declare mocks
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    FullName = "BitTitan.Runbooks.TestModule"
                }
            }
            elseif ($Script:numberTimesGetChildItemCalled -eq 1) {
                $Script:numberTimesGetChildItemCalled += 1
                return [PSCustomObject]@{
                    Name = "0.1.0"
                }
            }
            return [PSCustomObject]@{
                Name = "0.1.0"
            }
        }
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }
        mock Find-Module {
            return [PSCustomObject]@{
                Version = [Version]"0.1.0"
            }
        }

        it "only imports the module if the module currently installed is the latest version" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction Continue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Find-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Install-Module -Times 0 -Exactly -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }
    }

    context "when running on the default environment (Beta MSPComplete)" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-ChildItem
        $Script:numberTimesGetChildItemCalled = 0
        mock Get-ChildItem {
            if ($Script:numberTimesGetChildItemCalled -eq 0) {
                $Script:numberTimesGetChildItemCalled += 1
                return
            }
            return [PSCustomObject]@{
                Name = "0.1.0"
            }
        }

        # Mock Get-Module
        mock Get-Module {
            return [PSCustomObject]@{
                Path    = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
                Version = [Version]"0.1.0"
            }
        }

        # Mock Get-Item
        mock Get-Item {
            return [PSCustomObject]@{
                FullName = "FullName"
            }
        }

        # Mock Move-Item
        mock Move-Item {}

        # Set up the settings
        $Global:PSDefaultParameterValues = @{}

        it "installs and imports the module" {
            # Call the function
            Import-BT_Module BitTitan.Runbooks.TestModule -ErrorAction SilentlyContinue -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Install-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule.Beta"
            } -Scope it
            Assert-MockCalled Import-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\BitTitan.Runbooks.TestModule"
            } -Scope it
            Assert-MockCalled Get-Module -Times 1 -Exactly -ParameterFilter {
                $Name -eq "BitTitan.Runbooks.TestModule"
            } -Scope it

            # Verify the output
            $errorVariable | Should BeNullOrEmpty
        }
    }

    # Declare the functions to throw an exception
    $functionsToThrowExceptions = @(
        "Install-Module",
        "Import-Module",
        "Get-Module"
    )
    foreach ($function in $functionsToThrowExceptions) {
        context "when $($function) throws an exception" {
            # Mock Install-Module
            mock Install-Module {}

            # Mock Import-Module
            mock Import-Module {}

            # Mock Get-ChildItem
            mock Get-ChildItem {
                return $false
            }

            # Mock Get-Module
            mock Get-Module {
                return "module imported"
            }

            # Mock Get-Item
            mock Get-Item {
                return [PSCustomObject]@{
                    FullName = "FullName"
                }
            }

            # Mock Move-Item
            mock Move-Item {}

            # Mock the function to throw an exception
            mock $function {
                throw "throws exception"
            }

            it -TestCases @(
                @{
                    Environment = "Beta"
                    IsRunningOnLocalMachine = $false
                },
                @{
                    Environment = "Beta"
                    IsRunningOnLocalMachine = $true
                },
                @{
                    Environment = "BT"
                    IsRunningOnLocalMachine = $false
                },
                @{
                    Environment = "BT"
                    IsRunningOnLocalMachine = $true
                }
            ) "fails to install and/or import the module in the <Environment> environment and when IsRunningOnLocalMachine is <IsRunningOnLocalMachine>, and outputs an error." {
                param ($Environment, $IsRunningOnLocalMachine)

                # Set up the settings
                $Global:PSDefaultParameterValues["*-BT_*:Environment"] = $Environment
                $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $IsRunningOnLocalMachine

                # Call the function
                Import-BT_Module BitTitan.Runbooks.TestModule -Quiet -ErrorAction SilentlyContinue -ErrorVariable errorVariable

                # Verify the output
                # If running on local machine and the function to throw is Install-Module,
                # there will be no error output as Install-Module will not be called.
                if ($function -ne "Install-Module" -and !$IsRunningOnLocalMachine) {
                    $errorVariable | Should Not BeNullOrEmpty
                }
            }
        }
    }

    context "when the module fails to be imported" {
        # Mock Install-Module
        mock Install-Module {}

        # Mock Import-Module
        mock Import-Module {}

        # Mock Get-Module
        mock Get-Module {}

        # Mock Get-Item
        mock Get-Item {
            return [PSCustomObject]@{
                FullName = "FullName"
            }
        }

        # Mock Move-Item
        mock Move-Item {}

        it -TestCases @(
                @{
                    Environment = "Beta"
                    IsRunningOnLocalMachine = $false
                },
                @{
                    Environment = "Beta"
                    IsRunningOnLocalMachine = $true
                },
                @{
                    Environment = "BT"
                    IsRunningOnLocalMachine = $false
                },
                @{
                    Environment = "BT"
                    IsRunningOnLocalMachine = $true
                }
            ) "it outputs an error." {
                param ($Environment, $IsRunningOnLocalMachine)

                # Set up the settings
                $Global:PSDefaultParameterValues["*-BT_*:Environment"] = $Environment
                $Global:PSDefaultParameterValues["*-BT_*:IsRunningOnLocalMachine"] = $IsRunningOnLocalMachine

                # Call the function
                Import-BT_Module BitTitan.Runbooks.TestModule -Quiet -ErrorAction SilentlyContinue -ErrorVariable errorVariable

                # Verify the output
                $errorVariable | Should Not BeNullOrEmpty
            }
    }

    # Reset the BitTitan Runbook Environment settings
    $Global:PSDefaultParameterValues = $previousPSDefaultParameterValues
}