Functions/Get-PartnerCenterAccessToken.Tests.ps1

describe "BitTitan.Runbooks.PartnerCenter/Get-PartnerCenterAccessToken" -Tags "module", "unit" {

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

    # Declare our own external functions
    function Get-CredentialFromMSPCompleteEndpoint {
        param ($endpoint)
        return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
    }
    function Invoke-RestMethod {
        param ($Uri, $ContentType, $Body, $Method)
    }

    context "when there are no issues" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            return [PSCustomObject]@{
                expires_in      = 1000
                access_token    = "valid-token"
            }
        }

        it "retrieves the access token given the username and the password" {
            # Call the function
            $output = Get-PartnerCenterAccessToken -Username "username@domain.com" -Password ("password" | ConvertTo-SecureString -AsPlainText -Force) `
                -ApplicationId ([Guid]::empty).Guid

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 0 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 1 -Exactly -ParameterFilter {
                # Verify the URI
                $uriRegexMatch = [Regex]::new("https://login\.windows\.net/([\S]+)/oauth2/token").Match($Uri)
                $uriMatches = $uriRegexMatch.Groups[1].Value -eq "domain.com"

                # Verify the body
                Add-Type -AssemblyName System.Web
                $bodyRegexMatches = [Regex]::new("client_id=([\S]+)&username=([\S]+)&password=([\S]+)&scope=openid").Match($Body)
                $bodyMatches = $bodyRegexMatches.Groups[1].Value -eq ([Guid]::Empty).Guid -and `
                    $bodyRegexMatches.Groups[2].Value -eq "username%40domain.com" -and `
                    $bodyRegexMatches.Groups[3].Value -eq "password"

                $uriMatches -and $bodyMatches -and $ContentType -eq "application/x-www-form-urlencoded" -and $Method -eq "POST"
            } -Scope it

            # Verify the output
            $output | Should Be "valid-token"
        }

        it "retrieves the access token given the endpoint" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
                ExtendedProperties  = @{
                    ApplicationId = ([Guid]::empty).Guid
                }
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 1 -Exactly -ParameterFilter {
                # Verify the URI
                $uriRegexMatch = [Regex]::new("https://login\.windows\.net/([\S]+)/oauth2/token").Match($Uri)
                $uriMatches = $uriRegexMatch.Groups[1].Value -eq "domain.com"

                # Verify the body
                Add-Type -AssemblyName System.Web
                $bodyRegexMatches = [Regex]::new("client_id=([\S]+)&username=([\S]+)&password=([\S]+)&scope=openid").Match($Body)
                $bodyMatches = $bodyRegexMatches.Groups[1].Value -eq ([Guid]::Empty).Guid -and `
                    $bodyRegexMatches.Groups[2].Value -eq "username%40domain.com" -and `
                    $bodyRegexMatches.Groups[3].Value -eq "password"

                $uriMatches -and $bodyMatches -and $ContentType -eq "application/x-www-form-urlencoded" -and $Method -eq "POST"
            } -Scope it

            # Verify the output
            $output | Should Be "valid-token"
        }
    }

    context "when there is an exception while invoking the REST call" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            throw "throws exception"
        }

        it "fails to retrieve the access token and outputs an error" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
                ExtendedProperties  = @{
                    ApplicationId = ([Guid]::empty).Guid
                }
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint -ErrorAction SilentlyContinue `
                -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 1 -Exactly -ParameterFilter {
                # Verify the URI
                $uriRegexMatch = [Regex]::new("https://login\.windows\.net/([\S]+)/oauth2/token").Match($Uri)
                $uriMatches = $uriRegexMatch.Groups[1].Value -eq "domain.com"

                # Verify the body
                Add-Type -AssemblyName System.Web
                $bodyRegexMatches = [Regex]::new("client_id=([\S]+)&username=([\S]+)&password=([\S]+)&scope=openid").Match($Body)
                $bodyMatches = $bodyRegexMatches.Groups[1].Value -eq ([Guid]::Empty).Guid -and `
                    $bodyRegexMatches.Groups[2].Value -eq "username%40domain.com" -and `
                    $bodyRegexMatches.Groups[3].Value -eq "password"

                $uriMatches -and $bodyMatches -and $ContentType -eq "application/x-www-form-urlencoded" -and $Method -eq "POST"
            } -Scope it

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

    context "when a valid access token is not returned" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            return $null
        }

        it "fails to retrieve the access token and outputs an error" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
                ExtendedProperties  = @{
                    ApplicationId = ([Guid]::empty).Guid
                }
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint -ErrorAction SilentlyContinue `
                -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 1 -Exactly -ParameterFilter {
                # Verify the URI
                $uriRegexMatch = [Regex]::new("https://login\.windows\.net/([\S]+)/oauth2/token").Match($Uri)
                $uriMatches = $uriRegexMatch.Groups[1].Value -eq "domain.com"

                # Verify the body
                Add-Type -AssemblyName System.Web
                $bodyRegexMatches = [Regex]::new("client_id=([\S]+)&username=([\S]+)&password=([\S]+)&scope=openid").Match($Body)
                $bodyMatches = $bodyRegexMatches.Groups[1].Value -eq ([Guid]::Empty).Guid -and `
                    $bodyRegexMatches.Groups[2].Value -eq "username%40domain.com" -and `
                    $bodyRegexMatches.Groups[3].Value -eq "password"

                $uriMatches -and $bodyMatches -and $ContentType -eq "application/x-www-form-urlencoded" -and $Method -eq "POST"
            } -Scope it

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

    context "when the endpoint does not contain an 'ExtendedProperties' property" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            return [PSCustomObject]@{
                expires_in      = 1000
                access_token    = "valid-token"
            }
        }

        it "fails to retrieve the access token and outputs an error" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint -ErrorAction SilentlyContinue `
                -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 0 -Exactly -Scope it

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

    context "when the endpoint does not contain an 'ApplicationId' extended property" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username@domain.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            return [PSCustomObject]@{
                expires_in      = 1000
                access_token    = "valid-token"
            }
        }

        it "fails to retrieve the access token and outputs an error" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
                ExtendedProperties  = @{}
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint -ErrorAction SilentlyContinue `
                -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 0 -Exactly -Scope it

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

    context "when the username is not in the form of <user>@<domain>" {
        # Mock Get-CredentialFromMSPCompleteEndpoint
        mock Get-CredentialFromMSPCompleteEndpoint {
            return [PSCredential]::new("username.com", ("password" | ConvertTo-SecureString -AsPlainText -Force))
        }

        # Mock Invoke-RestMethod
        mock Invoke-RestMethod {
            return [PSCustomObject]@{
                expires_in      = 1000
                access_token    = "valid-token"
            }
        }

        it "fails to retrieve the access token and outputs an error" {
            # Mock the endpoint
            $endpoint = [PSCustomObject]@{
                Name                = "Stub endpoint"
                ExtendedProperties  = @{
                    ApplicationId = ([Guid]::empty).Guid
                }
            }

            # Call the function
            $output = Get-PartnerCenterAccessToken -Endpoint $endpoint -ErrorAction SilentlyContinue `
                -ErrorVariable errorVariable

            # Verify the mocks
            Assert-MockCalled Get-CredentialFromMSPCompleteEndpoint -Times 1 -Exactly -Scope it
            Assert-MockCalled Invoke-RestMethod -Times 0 -Exactly -Scope it

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