# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Import-Module HelpersCommon

Describe "Test-Connection" -tags "CI" {
    BeforeAll {
        $oldInformationPreference = $InformationPreference
        $oldProgressPreference = $ProgressPreference
        $InformationPreference = "Ignore"
        $ProgressPreference = "SilentlyContinue"

        $hostName = [System.Net.Dns]::GetHostName()
        $targetName = "localhost"
        $targetAddress = "127.0.0.1"
        # TODO:
        # CI Travis don't support IPv6
        # so we use the workaround.
        # $targetAddressIPv6 = "::1"
        $targetAddressIPv6 = [System.Net.Dns]::GetHostEntry($targetName).AddressList[0].IPAddressToString
        $UnreachableAddress = "10.11.12.13"
        # this resolves to an actual IP rather than 127.0.0.1
        # this can also include both IPv4 and IPv6, so select InterNetwork rather than InterNetworkV6
        $realAddress = [System.Net.Dns]::GetHostEntry($hostName).AddressList |
            Where-Object {$_.AddressFamily -eq "InterNetwork"} |
            Select-Object -First 1 |
            Foreach-Object {$_.IPAddressToString}
        # under some environments, we can't round trip this and retrieve the real name from the address
        # in this case we will simply use the hostname
        $jobContinues = Start-Job { Test-Connection $using:targetAddress -Continues }
    }

    AfterAll {
        $InformationPreference = $oldInformationPreference
        $ProgressPreference = $oldProgressPreference
    }

    Context "Ping" {
        It "Default parameter set is 'Ping'" {
            $result = Test-Connection $targetName
            $replies = $result.Replies

            $result.Count          | Should -Be 1
            $result[0]             | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+PingReport"
            $result[0].Source      | Should -BeExactly $hostName
            $result[0].Destination | Should -BeExactly $targetName

            $replies.Count           | Should -Be 4
            $replies[0]              | Should -BeOfType "System.Net.NetworkInformation.PingReply"
            $replies[0].Address      | Should -BeExactly $targetAddressIPv6
            $replies[0].Status       | Should -BeExactly "Success"
            # TODO: Here and below we skip the check on Unix because .Net Core issue
            if ($isWindows) {
                $replies[0].Buffer.Count | Should -Be 32
            }
        }

        It "Count parameter" {
            # Also we explicitly test '-Ping' parameter.
            $result1 = Test-Connection -Ping $targetName -Count 1
            $result2 = Test-Connection $targetName -Count 2

            $result1.Replies.Count | Should -Be 1
            $result2.Replies.Count | Should -Be 2
        }

        It "Quiet works" {
            $result1 = Test-Connection $targetName -Count 1 -Quiet
            # Ping unreachable address
            $result2 = Test-Connection $UnreachableAddress -Count 1 -Quiet

            $result1 | Should -BeTrue
            $result2 | Should -BeFalse
        }

        It "Ping fake host" {

            { $result = Test-Connection "fakeHost" -Count 1 -Quiet -ErrorAction Stop } | Should -Throw -ErrorId "TestConnectionException,Microsoft.PowerShell.Commands.TestConnectionCommand"
            # Error code = 11001 - Host not found.
            if (!$isWindows) {
                $Error[0].Exception.InnerException.ErrorCode | Should -Be -131073
            } else {
                $Error[0].Exception.InnerException.ErrorCode | Should -Be 11001
            }
        }

        # In VSTS, address is 0.0.0.0
        It "Force IPv4 with implicit PingOptions" {
            $result = Test-Connection $hostName -Count 1 -IPv4

            $result.Replies[0].Address              | Should -BeExactly $realAddress
            $result.Replies[0].Options.Ttl          | Should -BeLessOrEqual 128
            if ($isWindows) {
                $result.Replies[0].Options.DontFragment | Should -BeFalse
            }
        }

        # In VSTS, address is 0.0.0.0
        It "Force IPv4 with explicit PingOptions" {
            $result1 = Test-Connection $hostName -Count 1 -IPv4 -MaxHops 10 -DontFragment

            # explicitly go to google dns. this test will pass even if the destination is unreachable
            # it's more about breaking out of the loop
            $result2 = Test-Connection 8.8.8.8 -Count 1 -IPv4 -MaxHops 1 -DontFragment

            $result1.Replies[0].Address              | Should -BeExactly $realAddress
            # .Net Core (.Net Framework) returns Options based on default PingOptions() constructor (Ttl=128, DontFragment = false).
            # After .Net Core fix we should have 'DontFragment | Should -Be $true' here.
            $result1.Replies[0].Options.Ttl          | Should -BeLessOrEqual 128
            if (!$isWindows) {
                $result1.Replies[0].Options.DontFragment | Should -BeNullOrEmpty
                # depending on the network configuration any of the following should be returned
                $result2.Replies[0].Status               | Should -BeIn "TtlExpired","TimedOut","Success"
            } else {
                $result1.Replies[0].Options.DontFragment | Should -BeFalse
                # We expect 'TtlExpired' but if a router don't reply we get `TimedOut`
                # AzPipelines returns $null
                $result2.Replies[0].Status               | Should -BeIn "TtlExpired","TimedOut",$null
            }
        }

        It "Force IPv6" -Pending {
            $result = Test-Connection $targetName -Count 1 -IPv6

            $result.Replies[0].Address | Should -BeExactly $targetAddressIPv6
            # We should check Null not Empty!
            $result.Replies[0].Options | Should -Be $null
        }

        It "MaxHops Should -Be greater 0" {
            { Test-Connection $targetName -MaxHops 0 }  | Should -Throw -ErrorId "System.ArgumentOutOfRangeException,Microsoft.PowerShell.Commands.TestConnectionCommand"
            { Test-Connection $targetName -MaxHops -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
        }

        It "Count Should -Be greater 0" {
            { Test-Connection $targetName -Count 0 }  | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
            { Test-Connection $targetName -Count -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
        }

        It "Delay Should -Be greater 0" {
            { Test-Connection $targetName -Delay 0 }  | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
            { Test-Connection $targetName -Delay -1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
        }

        It "Delay works" {
            $result1 = measure-command {Test-Connection localhost -Count 2}
            $result2 = measure-command {Test-Connection localhost -Delay 4 -Count 2}

            $result1.TotalSeconds | Should -BeGreaterThan 1
            $result1.TotalSeconds | Should -BeLessThan 3
            $result2.TotalSeconds | Should -BeGreaterThan 4
        }

        It "BufferSize Should -Be between 0 and 65500" {
            { Test-Connection $targetName -BufferSize 0 }     | Should Not Throw
            { Test-Connection $targetName -BufferSize -1 }    | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
            { Test-Connection $targetName -BufferSize 65501 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand"
        }

        It "BufferSize works" -Pending:(!$IsWindows) {
            $result = Test-Connection $targetName -Count 1 -BufferSize 2

            if ($isWindows) {
                $result.Replies[0].Buffer.Count | Should -Be 2
            }
        }

        It "ResolveDestination for address" {
            $result = Test-Connection $targetAddress -ResolveDestination -Count 1
            $resolvedName = [System.Net.DNS]::GetHostEntry($targetAddress).HostName

            $result.Destination | Should -BeExactly $resolvedName
            $result.Replies[0].Address     | Should -BeExactly $targetAddress
        }

        It "ResolveDestination for name" {
            $result = Test-Connection $targetName -ResolveDestination -Count 1
            $resolvedName = [System.Net.DNS]::GetHostByName($targetName).HostName

            # PingReply in CoreFX doesn't return ScopeId in IPAddress (Bug?)
            # but GetHostAddresses() returns so remove it.
            $resolvedAddress = ([System.Net.DNS]::GetHostAddresses($resolvedName)[0] -split "%")[0]

            $result.Destination | Should -BeExactly $resolvedName
            $result.Replies[0].Address     | Should -BeExactly $resolvedAddress
        }

        It "TimeOut works" {
            (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 1 }).TotalSeconds | Should -BeLessThan 3
            (Measure-Command { Test-Connection $UnreachableAddress -Count 1 -TimeOut 4 }).TotalSeconds | Should -BeGreaterThan 3
        }

        It "Continues works" {
            # By default we do 4 ping so for '-Continues' we expect to get >4 results.
            # Also we should wait >4 seconds before check results but previous tests already did the pause.
            $result = Receive-Job $jobContinues
            Remove-Job $jobContinues -Force

            $result.Count           | Should -BeGreaterThan 4
            $result[0].Address      | Should -BeExactly $targetAddress
            $result[0].Status       | Should -BeExactly "Success"
            if ($isWindows) {
                $result[0].Buffer.Count | Should -Be 32
            }
        }
}

    # TODO: We skip the MTUSizeDetect tests on Unix because we expect 'TtlExpired' but get 'TimeOut' internally from .Net Core
    Context "MTUSizeDetect" {
        It "MTUSizeDetect works" -pending:($IsMacOS) {
            $result = Test-Connection $hostName -MTUSizeDetect

            $result | Should -BeOfType "System.Net.NetworkInformation.PingReply"
            $result.Destination | Should -BeExactly $hostName
            $result.Status | Should -BeExactly "Success"
            $result.MTUSize | Should -BeGreaterThan 0
        }

        It "Quiet works" -pending:($IsMacOS) {
            $result = Test-Connection $hostName -MTUSizeDetect -Quiet

            $result | Should -BeOfType "Int32"
            $result | Should -BeGreaterThan 0
        }
    }

    Context "TraceRoute" {
        It "TraceRoute works" {
            # real address is an ipv4 address, so force IPv4
            $result = Test-Connection $hostName -TraceRoute -IPv4
            $replies = $result.Replies
            # Check target host reply.
            $pingReplies = $replies[-1].PingReplies

            $result.Count              | Should -Be 1
            $result                    | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceRouteResult"
            $result.Source             | Should -BeExactly $hostName
            $result.DestinationAddress | Should -BeExactly $realAddress
            $result.DestinationHost    | Should -BeExactly $hostName

            $replies.Count               | Should -BeGreaterThan 0
            $replies[0]                  | Should -BeOfType "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceRouteReply"
            $replies[0].Hop              | Should -Be 1

            $pingReplies.Count           | Should -Be 3
            $pingReplies[0].Address      | Should -BeExactly $realAddress
            $pingReplies[0].Status       | Should -BeExactly "Success"
            if (!$isWindows) {
                $pingReplies[0].Buffer.Count | Should -Match '^0$|^32$'
            } else {
                $pingReplies[0].Buffer.Count | Should -Be 32
            }
        }

        It "Quiet works" {
            $result = Test-Connection $hostName -TraceRoute -Quiet 6> $null

            $result | Should -BeTrue
        }
    }
}

Describe "Connection" -Tag "CI", "RequireAdminOnWindows" {
    BeforeAll {
        # Ensure the local host listen on port 80
        $WebListener = Start-WebListener
        $UnreachableAddress = "10.11.12.13"
    }

    It "Test connection to local host port 80" {
        Test-Connection '127.0.0.1' -TCPPort $WebListener.HttpPort | Should -BeTrue
    }

    It "Test connection to unreachable host port 80" {
        Test-Connection $UnreachableAddress -TCPPort 80 -TimeOut 1 | Should -BeFalse
    }
}
