Tests/Test-ObjectGraph.Tests.ps1

#Requires -Modules @{ModuleName="Pester"; ModuleVersion="5.5.0"}

using module ..\..\ObjectGraphTools

[Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'False positive')]

param()

Describe 'Test-Object' {

    BeforeAll {

        Set-StrictMode -Version Latest

        $Person = [PSCustomObject]@{
            FirstName = 'John'
            LastName  = 'Smith'
            IsAlive   = $True
            Birthday  = [DateTime]'Monday, October 7, 1963 10:47:00 PM'
            Age       = 27
            Address   = [PSCustomObject]@{
                Street     = '21 2nd Street'
                City       = 'New York'
                State      = 'NY'
                PostalCode = '10021-3100'
            }
            Phone = @{
                Home   = '212 555-1234'
                Mobile = '212 555-2345'
                Work   = '212 555-3456', '212 555-3456', '646 555-4567'
            }
            Children = @('Dennis', 'Stefan')
            Spouse = $Null
        }
    }

    Context 'Existence Check' {

        It 'Help' {
            Test-Object -? | Out-String -Stream | Should -Contain SYNOPSIS
        }
    }

    Context 'Type (as string)' {

        It 'Report' {
            $True | Test-Object @{ '@Type' = 'Bool' } | Should -BeNullOrEmpty
            $Report = 123 | Test-ObjectGraph @{ '@Type' = 'Bool' } -Elaborate
            $Report.ObjectNode.Value | Should -Be 123
            $Report.Valid            | Should -Be $False
            $Report.Issue            | Should -Not -BeNullOrEmpty
        }

        It 'Bool' {
            $True  | Test-Object @{ '@Type' = 'Bool' } -ValidateOnly | Should -BeTrue
            'True' | Test-Object @{ '@Type' = 'Bool' } -ValidateOnly | Should -BeFalse
        }

        It 'Int' {
            123    | Test-Object @{ '@Type' = 'Int' } -ValidateOnly | Should -BeTrue
            '123'  | Test-Object @{ '@Type' = 'Int' } -ValidateOnly | Should -BeFalse
        }

        It 'String' {
            'True' | Test-Object @{ '@Type' = 'String' } -ValidateOnly | Should -BeTrue
            '123'  | Test-Object @{ '@Type' = 'String' } -ValidateOnly | Should -BeTrue
            123    | Test-Object @{ '@Type' = 'String' } -ValidateOnly | Should -BeFalse
        }

        It 'Array' {
            ,@(1,2)    | Test-Object @{ '@Type' = 'Array'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@(1)      | Test-Object @{ '@Type' = 'Array'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@()       | Test-Object @{ '@Type' = 'Array' }                             -ValidateOnly | Should -BeTrue
            'Test'     | Test-Object @{ '@Type' = 'Array'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            @{ a = 1 } | Test-Object @{ '@Type' = 'Array'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }

        It 'HashTable' {
            @{ a = 1 } | Test-Object @{ '@Type' = 'HashTable'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            @{}        | Test-Object @{ '@Type' = 'HashTable' }                             -ValidateOnly | Should -BeTrue
            'Test'     | Test-Object @{ '@Type' = 'HashTable'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            ,@(1, 2)   | Test-Object @{ '@Type' = 'HashTable'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }
    }

    Context 'Type (as type)' {

        It 'Report' {
            $True | Test-Object @{ '@Type' = [Bool] } | Should -BeNullOrEmpty
            123   | Test-Object @{ '@Type' = [Bool] } -Elaborate | ForEach-Object {
                $_.ObjectNode.Value | Should -Be 123
                $_.Valid            | Should -Be $False
                $_.Issue            | Should -Not -BeNullOrEmpty
            }
        }

        It 'Bool' {
            $True  | Test-Object @{ '@Type' = [Bool] }    -ValidateOnly | Should -BeTrue
            'True' | Test-Object @{ '@Type' = [Bool] }    -ValidateOnly | Should -BeFalse
        }

        It 'Int' {
            123    | Test-Object @{ '@Type' = [Int] }     -ValidateOnly | Should -BeTrue
            '123'  | Test-Object @{ '@Type' = [Int] }     -ValidateOnly | Should -BeFalse
        }

        It 'String' {
            'True' | Test-Object @{ '@Type' = [String] } -ValidateOnly | Should -BeTrue
            '123'  | Test-Object @{ '@Type' = [String] } -ValidateOnly | Should -BeTrue
            123    | Test-Object @{ '@Type' = [String] } -ValidateOnly | Should -BeFalse
        }

        It 'Array' {
            ,@(1,2)    | Test-Object @{ '@Type' = [Array]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@(1)      | Test-Object @{ '@Type' = [Array]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@()       | Test-Object @{ '@Type' = [Array] }                             -ValidateOnly | Should -BeTrue
            'Test'     | Test-Object @{ '@Type' = [Array]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            @{ a = 1 } | Test-Object @{ '@Type' = [Array]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }

        It 'HashTable' {
            @{ a = 1 } | Test-Object @{ '@Type' = [HashTable]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            @{}        | Test-Object @{ '@Type' = [HashTable] }                             -ValidateOnly | Should -BeTrue
            'Test'     | Test-Object @{ '@Type' = [HashTable]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            ,@(1, 2)   | Test-Object @{ '@Type' = [HashTable]; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }
    }

    Context 'Not Type (as string)' {

        It 'Bool' {
            'True' | Test-Object @{ '@NotType' = 'Bool' }    -ValidateOnly | Should -BeTrue
            $True  | Test-Object @{ '@NotType' = 'Bool' }    -ValidateOnly | Should -BeFalse
        }
    }

    Context 'Not Type (as type)' {

        It 'Not Bool' {
            'True' | Test-Object @{ '@NotType' = [Bool] }    -ValidateOnly | Should -BeTrue
            $True  | Test-Object @{ '@NotType' = [Bool] }    -ValidateOnly | Should -BeFalse
        }
    }


    Context 'Multiple types' {

        It 'Any of type' {
            '123' | Test-Object @{ '@Type' = [Int], [String] } -ValidateOnly | Should -BeTrue
            $true | Test-Object @{ '@Type' = [Int], [String] } -ValidateOnly | Should -BeFalse
        }

        It 'None of type' {
            '123' | Test-Object @{ '@NotType' = [Int], [String] } -ValidateOnly | Should -BeFalse
            $true | Test-Object @{ '@NotType' = [Int], [String] } -ValidateOnly | Should -BeTrue
        }
    }

    Context 'No type' {

        It '$Null' {
            @{ Test = '123' } | Test-Object @{ Test = @{ '@Type' = [Int], [String] } }         -ValidateOnly | Should -BeTrue
            @{ Test = $Null } | Test-Object @{ Test = @{ '@Type' = [Int], [String] } }         -ValidateOnly | Should -BeFalse
            @{ Test = $Null } | Test-Object @{ Test = @{ '@Type' = [Int], [String], [Void] } } -ValidateOnly | Should -BeTrue
            @{ Test = $Null } | Test-Object @{ Test = @{ '@Type' = [Int], [String], 'Null' } } -ValidateOnly | Should -BeTrue
            @{ Test = $Null } | Test-Object @{ Test = @{ '@Type' = [Int], [String], $Null } }  -ValidateOnly | Should -BeTrue
            @{ Test = '123' } | Test-Object @{ Test = @{ '@Type' = [Int], [Void] } }           -ValidateOnly | Should -BeFalse
        }
    }

    Context 'PSNode Type' {

        It 'Value' {
            'String' | Test-Object @{ '@Type' = 'PSNode' }           -ValidateOnly | Should -BeTrue
            'String' | Test-Object @{ '@Type' = 'PSLeafNode' }       -ValidateOnly | Should -BeTrue
            'String' | Test-Object @{ '@Type' = 'PSCollectionNode' } -ValidateOnly | Should -BeFalse
            'String' | Test-Object @{ '@Type' = 'PSListNode' }       -ValidateOnly | Should -BeFalse
            'String' | Test-Object @{ '@Type' = 'PSMapNode' }        -ValidateOnly | Should -BeFalse
            'String' | Test-Object @{ '@Type' = 'PSObjectNode' }     -ValidateOnly | Should -BeFalse
        }

        It 'Array' {
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSNode';           '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSLeafNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSCollectionNode'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSListNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSMapNode';        '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            ,@(1,2,3) | Test-Object @{ '@Type' = 'PSObjectNode';     '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }

        It 'Dictionary' {
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSNode';           '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSLeafNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSCollectionNode'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSListNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSMapNode';        '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            @{ a = 1 } | Test-Object @{ '@Type' = 'PSObjectNode';     '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
        }

        It 'Object' {
            $Person | Test-Object @{ '@Type' = 'PSNode';           '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            $Person | Test-Object @{ '@Type' = 'PSLeafNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            $Person | Test-Object @{ '@Type' = 'PSCollectionNode'; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            $Person | Test-Object @{ '@Type' = 'PSListNode';       '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeFalse
            $Person | Test-Object @{ '@Type' = 'PSMapNode';        '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            $Person | Test-Object @{ '@Type' = 'PSObjectNode';     '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
        }
    }

    Context 'Natural list ' {
        BeforeAll {
            $Schema = @{ Word = @{ '@Match' = '^\w+$' } }
        }
        it 'No list'         { @{}                           | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it '$Null'           { @{ Word = $null }             | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Test'            { @{ Word = 'Test' }            | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Empty'           { @{ Word = @() }               | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Single'          { @{ Word = @('a') }            | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Multiple'        { @{ Word = @('a', 'b') }       | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'No match a b'    { @{ Word = @('a b') }          | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a, b c' { @{ Word = @('a', 'b c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a b, c' { @{ Word = @('a b', 'c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Dictionary'      { @{ Word = @{ a = 'b' } }      | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'IsWord item'     { @{ Word = @{ IsWord = 'b' } } | Test-Object $Schema -ValidateOnly | Should -BeTrue }
    }

    Context 'Compulsory list with unnamed items' {
        BeforeAll {
            $Schema = @{ Word = @(@{ '@Match' = '^\w+$' }) }
        }
        it 'No list'         { @{}                           | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it '$Null'           { @{ Word = $null }             | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Test'            { @{ Word = 'Test' }            | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Empty'           { @{ Word = @() }               | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Single'          { @{ Word = @('a') }            | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Multiple'        { @{ Word = @('a', 'b') }       | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'No match a b'    { @{ Word = @('a b') }          | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a, b c' { @{ Word = @('a', 'b c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a b, c' { @{ Word = @('a b', 'c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Dictionary'      { @{ Word = @{ a = 'b' } }      | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'IsWord item'     { @{ Word = @{ IsWord = 'b' } } | Test-Object $Schema -ValidateOnly | Should -BeFalse }
    }

    Context 'Compulsory list with named items' {
        BeforeAll {
            $Schema = @{ Word = @{ '@Type' = [PSListNode]; IsWord = @{ '@Match' = '^\w+$' } } }
        }
        it 'No list'         { @{}                           | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it '$Null'           { @{ Word = $null }             | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Test'            { @{ Word = 'Test' }            | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Empty'           { @{ Word = @() }               | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Single'          { @{ Word = @('a') }            | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Multiple'        { @{ Word = @('a', 'b') }       | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'No match a b'    { @{ Word = @('a b') }          | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a, b c' { @{ Word = @('a', 'b c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a b, c' { @{ Word = @('a b', 'c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Dictionary'      { @{ Word = @{ a = 'b' } }      | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'IsWord item'     { @{ Word = @{ IsWord = 'b' } } | Test-Object $Schema -ValidateOnly | Should -BeFalse }
    }

    Context 'Forced list with named items' {
        BeforeAll {
            $Schema = @{ Word = @{ '@Type' = [PSListNode]; '@Required' = $true; IsWord = @{ '@Match' = '^\w+$' } } }
        }
        it 'No list'         { @{}                           | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it '$Null'           { @{ Word = $null }             | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Test'            { @{ Word = 'Test' }            | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Empty'           { @{ Word = @() }               | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Single'          { @{ Word = @('a') }            | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'Multiple'        { @{ Word = @('a', 'b') }       | Test-Object $Schema -ValidateOnly | Should -BeTrue }
        it 'No match a b'    { @{ Word = @('a b') }          | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a, b c' { @{ Word = @('a', 'b c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'No match a b, c' { @{ Word = @('a b', 'c') }     | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'Dictionary'      { @{ Word = @{ a = 'b' } }      | Test-Object $Schema -ValidateOnly | Should -BeFalse }
        it 'IsWord item'     { @{ Word = @{ IsWord = 'b' } } | Test-Object $Schema -ValidateOnly | Should -BeFalse }
    }

    Context 'Multiple integer Limits' {

        It 'Maximum int' {
            ,@(17, 18, 19) | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(40, 41, 42) | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(17, 42, 99) | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive maximum int' {
            ,@(17, 18, 19) | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(40, 41, 42) | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeFalse
            ,@(17, 42, 99) | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Minimum int' {
            ,@(97, 98, 99) | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(42, 43, 44) | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(17, 42, 99) | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive minimum int' {
            ,@(97, 98, 99) | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeTrue
            ,@(42, 43, 44) | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeFalse
            ,@(17, 42, 99) | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeFalse
        }
    }

    Context 'String limits' {

        It 'Maximum string' {
            'Alpha' | Test-Object @{ '@Maximum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Beta'  | Test-Object @{ '@Maximum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Gamma' | Test-Object @{ '@Maximum' = 'Beta' } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive maximum string' {
            'Alpha' | Test-Object @{ '@ExclusiveMaximum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Beta'  | Test-Object @{ '@ExclusiveMaximum' = 'Beta' } -ValidateOnly | Should -BeFalse
            'Gamma' | Test-Object @{ '@ExclusiveMaximum' = 'Beta' } -ValidateOnly | Should -BeFalse
        }

        It 'Minimum string' {
            'Gamma' | Test-Object @{ '@Minimum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Beta'  | Test-Object @{ '@Minimum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Alpha' | Test-Object @{ '@Minimum' = 'Beta' } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive minimum string' {
            'Gamma' | Test-Object @{ '@ExclusiveMinimum' = 'Beta' } -ValidateOnly | Should -BeTrue
            'Beta'  | Test-Object @{ '@ExclusiveMinimum' = 'Beta' } -ValidateOnly | Should -BeFalse
            'Alpha' | Test-Object @{ '@ExclusiveMinimum' = 'Beta' } -ValidateOnly | Should -BeFalse
        }
    }

    Context 'Integer Limits' {

        It 'Maximum int' {
            17 | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeTrue
            42 | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeTrue
            99 | Test-Object @{ '@Maximum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive maximum int' {
            17 | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeTrue
            42 | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeFalse
            99 | Test-Object @{ '@ExclusiveMaximum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Minimum int' {
            99 | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeTrue
            42 | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeTrue
            17 | Test-Object @{ '@Minimum' = 42 } -ValidateOnly | Should -BeFalse
        }

        It 'Exclusive minimum int' {
            99 | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeTrue
            42 | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeFalse
            17 | Test-Object @{ '@ExclusiveMinimum' = 42 } -ValidateOnly | Should -BeFalse
        }
    }


    Context 'Case sensitive string limits' {

        It 'Maximum string' {
            'alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Maximum' = 'Alpha' } -ValidateOnly | Should -BeTrue
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Maximum' = 'Alpha' } -ValidateOnly | Should -BeTrue
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Maximum' = 'alpha' } -ValidateOnly | Should -BeFalse
        }

        It 'Maximum exclusive string' {
            'alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMaximum' = 'Alpha' } -ValidateOnly | Should -BeTrue
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMaximum' = 'Alpha' } -ValidateOnly | Should -BeFalse
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMaximum' = 'alpha' } -ValidateOnly | Should -BeFalse
        }

        It 'Minimum string' {
            'alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Minimum' = 'Alpha' } -ValidateOnly | Should -BeFalse
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Minimum' = 'Alpha' } -ValidateOnly | Should -BeTrue
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@Minimum' = 'alpha' } -ValidateOnly | Should -BeTrue
        }

        It 'Minimum exclusive string' {
            'alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMinimum' = 'Alpha' } -ValidateOnly | Should -BeFalse
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMinimum' = 'Alpha' } -ValidateOnly | Should -BeFalse
            'Alpha' | Test-Object @{ '@CaseSensitive' = $true; '@ExclusiveMinimum' = 'alpha' } -ValidateOnly | Should -BeTrue
        }
    }

    Context 'String length limits' {

        It 'Minimum length' {
            'abc'   | Test-Object @{ '@MinimumLength' = 4 } -ValidateOnly | Should -BeFalse
            'abcd'  | Test-Object @{ '@MinimumLength' = 4 } -ValidateOnly | Should -BeTrue
        }

        It 'Length' {
            'ab'    | Test-Object @{ '@Length' = 3 } -ValidateOnly | Should -BeFalse
            'abc'   | Test-Object @{ '@Length' = 3 } -ValidateOnly | Should -BeTrue
            'abcd'  | Test-Object @{ '@Length' = 3 } -ValidateOnly | Should -BeFalse
        }

        It 'Maximum length' {
            'abc'   | Test-Object @{ '@MaximumLength' = 3 } -ValidateOnly | Should -BeTrue
            'abcd'  | Test-Object @{ '@MaximumLength' = 3 } -ValidateOnly | Should -BeFalse
        }

        It 'Multiple values length' {
            ,@('12', '345', '6789') | Test-Object @{ '@MinimumLength' = 2 } -ValidateOnly | Should -BeTrue
            ,@('12', '345', '6789') | Test-Object @{ '@MinimumLength' = 3 } -ValidateOnly | Should -BeFalse
            ,@('123', '456', '789') | Test-Object @{ '@Length' = 3 }        -ValidateOnly | Should -BeTrue
            ,@('12', '345', '6789') | Test-Object @{ '@Length' = 3 }        -ValidateOnly | Should -BeFalse
            ,@('12', '345', '6789') | Test-Object @{ '@MaximumLength' = 4 } -ValidateOnly | Should -BeTrue
            ,@('12', '345', '6789') | Test-Object @{ '@MaximumLength' = 3 } -ValidateOnly | Should -BeFalse
        }
    }

    Context 'Patterns' {

        It 'Like' {
            'test' | Test-Object @{ '@Like' = 'T*t' } -ValidateOnly | Should -BeTrue
            'test' | Test-Object @{ '@Like' = 'T?t' } -ValidateOnly | Should -BeFalse
        }

        It 'Not like' {
            'test' | Test-Object @{ '@NotLike' = 'T*t' } -ValidateOnly | Should -BeFalse
            'test' | Test-Object @{ '@NotLike' = 'T?t' } -ValidateOnly | Should -BeTrue
        }


        It 'Match' {
            'test' | Test-Object @{ '@Match' = 'T.*t' } -ValidateOnly | Should -BeTrue
            'test' | Test-Object @{ '@Match' = 'T.t' } -ValidateOnly | Should -BeFalse
        }

        It 'Not match' {
            'test' | Test-Object @{ '@NotMatch' = 'T.*t' } -ValidateOnly | Should -BeFalse
            'test' | Test-Object @{ '@NotMatch' = 'T.t' }   -ValidateOnly | Should -BeTrue
        }
    }

    Context 'Case sensitive patterns' {

        It 'Like' {
            'Test' | Test-Object @{ '@CaseSensitive' = $true; '@Like' = 'T*t' } -ValidateOnly | Should -BeTrue
            'test' | Test-Object @{ '@CaseSensitive' = $true; '@Like' = 'T*t' } -ValidateOnly | Should -BeFalse
        }

        It 'Not like' {
            'Test' | Test-Object @{ '@CaseSensitive' = $true; '@notLike' = 'T*t' } -ValidateOnly | Should -BeFalse
            'test' | Test-Object @{ '@CaseSensitive' = $true; '@notLike' = 'T*t' } -ValidateOnly | Should -BeTrue
        }


        It 'Match' {
            'Test' | Test-Object @{ '@CaseSensitive' = $true; '@Match' = 'T..t' } -ValidateOnly | Should -BeTrue
            'test' | Test-Object @{ '@CaseSensitive' = $true; '@Match' = 'T..t' } -ValidateOnly | Should -BeFalse
        }

        It 'Not match' {
            'Test' | Test-Object @{ '@CaseSensitive' = $true; '@notMatch' = 'T..t' } -ValidateOnly | Should -BeFalse
            'test' | Test-Object @{ '@CaseSensitive' = $true; '@notMatch' = 'T..t' } -ValidateOnly | Should -BeTrue
        }
    }

    Context 'Multiple patterns' {

        It 'Like' {
            'Two'  | Test-Object @{ '@Like' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeTrue
            'Four' | Test-Object @{ '@Like' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeFalse
        }

        It 'Not like' {
            'Two'  | Test-Object @{ '@NotLike' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeFalse
            'Four' | Test-Object @{ '@NotLike' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeTrue
        }


        It 'Match' {
            'Two'  | Test-Object @{ '@Match' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeTrue
            'Four' | Test-Object @{ '@Match' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeFalse
        }

        It 'Not match' {
            'Two'  | Test-Object @{ '@NotMatch' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeFalse
            'Four' | Test-Object @{ '@NotMatch' = 'One', 'Two', 'Three' } -ValidateOnly | Should -BeTrue
        }
    }


    Context 'No (child) Node' {

        It "[V] Leaf node" {
            'Test' | Test-Object @{} -ValidateOnly | Should -BeTrue
            'Test' | Test-Object @{} | Should -BeNullOrEmpty
        }

        It "[V] Empty list node" {
            ,@() | Test-Object @{} -ValidateOnly | Should -BeTrue
            ,@() | Test-Object @{} | Should -BeNullOrEmpty
        }

        It "[X] Simple list node" {
            ,@('Test') | Test-Object @{} -ValidateOnly | Should -BeFalse
            $Result = ,@('Test') | Test-Object @{}
            $Result                 | Should -BeOfType PSCustomObject
            $Result.Valid           | Should -BeFalse
            $Result.ObjectNode.Path | Should -BeNullOrEmpty
            $Result.Issue           | Should -BeLike '*not accepted*0*'
        }

        It "[X] Simple list node" {
            ,@('a', 'b') | Test-Object @{} -ValidateOnly | Should -BeFalse
            ,@('a', 'b') | Test-Object @{}| Should -not -BeNullOrEmpty
        }

        It "[V] Empty map node" {
            @{} | Test-Object @{} -ValidateOnly | Should -BeTrue
            @{} | Test-Object @{} | Should -BeNullOrEmpty
        }

        It "[X] Simple map node" {
            @{ a = 1 } | Test-Object @{} -ValidateOnly | Should -BeFalse
            $Result = @{ a = 1 } | Test-Object @{}
            $Result                 | Should -BeOfType PSCustomObject
            $Result.Valid           | Should -BeFalse
            $Result.ObjectNode.Path | Should -BeNullOrEmpty
            $Result.Issue           | Should -BeLike "*not accepted*a*"
        }

        It "[X] Complex object" {
            $Person | Test-Object @{} -ValidateOnly | Should -BeFalse
            $Result = $Person | Test-Object @{}
            $Result                 | Should -BeOfType PSCustomObject
            $Result.Valid           | Should -BeFalse
            $Result.ObjectNode.Path | Should -BeNullOrEmpty
            $Result.Issue           | Should -BeLike "*not accepted*FirstName*LastName*"
        }


        It "[X] Complex map node" {
            $Person | Test-Object @{ '@type' = [PSMapNode] } -ValidateOnly | Should -BeFalse
            $Result = $Person | Test-Object @{ '@type' = [PSMapNode] }
            $Result                 | Should -BeOfType PSCustomObject
            $Result.Valid           | Should -BeFalse
            $Result.ObjectNode.Path | Should -BeNullOrEmpty
            $Result.Issue           | Should -BeLike "*not accepted*FirstName*LastName*"
        }
    }

    Context 'Any (child) Node' {

        It "[V] Leaf node" {
            'Test' | Test-Object @() -ValidateOnly | Should -BeTrue
            'Test' | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Empty list node" {
            ,@() | Test-Object @() -ValidateOnly | Should -BeTrue
            ,@() | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Simple list node" {
            ,@('Test') | Test-Object @() -ValidateOnly | Should -BeTrue
            ,@('Test') | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Simple list node" {
            ,@('a', 'b') | Test-Object @() -ValidateOnly | Should -BeTrue
            ,@('a', 'b') | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Empty map node" {
            @{} | Test-Object @() -ValidateOnly | Should -BeTrue
            @{} | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Simple map node" {
            @{ a = 1 } | Test-Object @() -ValidateOnly | Should -BeTrue
            @{ a = 1 } | Test-Object @() | Should -BeNullOrEmpty
        }

        It "[V] Complex object" {
            $Person | Test-Object @() -ValidateOnly | Should -BeTrue
            $Person | Test-Object @() | Should -BeNullOrEmpty
        }


        It "[V] Complex map node" {
            $Person | Test-Object @{ '@type' = [PSMapNode]; '*' = @() } -ValidateOnly | Should -BeFalse
            $Person | Test-Object @{ '@type' = [PSMapNode]; '@AllowExtraNodes' = $true } | Should -BeNullOrEmpty
        }
    }

    Context 'Map nodes' {

        It "[V] Single name" {
            $Person | Test-Object @{ Age = @{ '@Type' = 'Int' }; '@AllowExtraNodes' = $true } -ValidateOnly | Should -BeTrue
            $Person | Test-Object @{ Age = @{ '@Type' = 'Int' }; '@AllowExtraNodes' = $true } | Should -BeNullOrEmpty
        }

        It "[V] Multiple names" {
            $Schema = @{
                FirstName = @{ '@Type' = 'String' }
                LastName  = @{ '@Type' = 'String' }
                IsAlive   = @{ '@Type' = 'Bool' }
                Birthday  = @{ '@Type' = 'DateTime' }
                Age       = @{ '@Type' = 'Int' }
                '@AllowExtraNodes' = $true
            }
            $Person | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Person | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] All (root) names defined" {
            $Schema = @{
                FirstName = @{ '@Type' = 'String' }
                LastName  = @{ '@Type' = 'String' }
                IsAlive   = @{ '@Type' = 'Bool' }
                Birthday  = @{ '@Type' = 'DateTime' }
                Age       = @{ '@Type' = 'Int' }
                Address   = @{ '@Type' = 'PSMapNode',  $Null; '@AllowExtraNodes' = $true }
                Phone     = @{ '@Type' = 'PSMapNode',  $Null; '@AllowExtraNodes' = $true }
                Children  = @{ '@Type' = 'PSListNode', $Null; '@AllowExtraNodes' = $true }
                Spouse    = @{ '@Type' = 'String',     $Null }
            }
            $Person | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Person | Test-Object $Schema | Should -BeNullOrEmpty
        }
    }

    Context 'List nodes' {

        BeforeAll {

            $Data = @{
                fruits = @(
                    'apple',
                    'orange',
                    'pear'
                )
                vegetables = @(
                    @{
                        veggieName = 'potato'
                        veggieLike = $True
                    },
                    @{
                        veggieName = 'broccoli'
                        veggieLike = $False
                    }
                )
            }

            $Data2 = @{
                fruits = @(
                    'apple',
                    'orange',
                    'pear'
                )
                vegetables = @(
                    @{
                        veggieName = 'potato'
                        veggieLike = $True
                    },
                    @{
                        veggieName = 'broccoli'
                        veggieLike = $False
                    },
                    @{ # Duplicate node
                        veggieName = 'potato'
                        veggieLike = $True
                    }
                )
            }
        }

        It "[X] One specific node, not allowing extra nodes" {
            $Schema = @{
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeFalse
            $Data | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        It "[V] One specific node and allowing extra nodes" {
            $Schema = @{
                '@AllowExtraNodes' = $true
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] One specific node and allowing extra nodes" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    '@AllowExtraNodes' = $true
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Single node that match a test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[X] Multiple nodes that match at least one single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        '@AllowExtraNodes' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Multiple nodes that match a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Multiple nodes that match a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie1 = @{
                        '@Type' = [PSMapNode]
                        '@Unique' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                    veggie2 = @{
                        '@Type' = [PSMapNode]
                        '@Unique' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Multiple nodes that match a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data2 | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data2 | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Match at least one node in a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        '@AllowExtraNodes' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data2 | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data2 | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Duplicate nodes that match a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    '@AllowExtraNodes' = $true
                    veggie = @{
                        '@Type' = [PSMapNode]
                        '@Unique' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data2 | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data2 | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[X] Duplicate nodes that match a single test definition" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie = @{
                        '@Type' = [PSMapNode]
                        '@Unique' = $true
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data2 | Test-Object $Schema -ValidateOnly | Should -BeFalse
            $Data2 | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        It "[V] Multiple nodes that match equal test definitions" {
            $Schema = @{
                fruits = @{
                    '@Type' = [PSListNode]
                    fruit = @{ '@Type' = [String] }
                }
                vegetables = @{
                    '@Type' = [PSListNode]
                    veggie1 = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                    veggie2 = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                    veggie3 = @{
                        '@Type' = [PSMapNode]
                        veggieName = @{ '@Type' = [String] }
                        veggieLike = @{ '@Type' = [Bool] }
                    }
                }
            }
            $Data2 | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data2 | Test-Object $Schema | Should -BeNullOrEmpty
        }

        It "[V] Full assert test" {
            $Schema = @{
                FirstName = @{ '@Type' = 'String' }
                LastName  = @{ '@Type' = 'String' }
                IsAlive   = @{ '@Type' = 'Bool' }
                Birthday  = @{ '@Type' = 'DateTime' }
                Age       = @{
                    '@Type' = 'Int'
                    '@Minimum' = 0
                    '@Maximum' = 99
                }
                Address = @{
                    '@Type' = 'PSMapNode'
                    Street     = @{ '@Type' = 'String' }
                    City       = @{ '@Type' = 'String' }
                    State      = @{ '@Type' = 'String' }
                    PostalCode = @{ '@Type' = 'String' }
                }
                Phone = @{
                    '@Type' = 'PSMapNode',  $Null
                    Home    = @{ '@Match' = '^\d{3} \d{3}-\d{4}$' }
                    Mobile  = @{ '@Match' = '^\d{3} \d{3}-\d{4}$' }
                    Work    = @{ '@Match' = '^\d{3} \d{3}-\d{4}$' }
                }
                Children  = @(@{ '@Type' = 'String', $Null })
                Spouse    = @{ '@Type' = 'String', $Null }
            }
            $Person | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Person | Test-Object $Schema | Should -BeNullOrEmpty
        }
    }

    Context 'Required node' {

        $Schema = @{
            Id = @{ '@Type' = 'Int'; '@Required' = $true }
            Data = @{ '@Type' = 'String' }
        }

        @{ Id = 42 }                | Test-Object $Schema -ValidateOnly | Should -BeTrue
        @{ Id = 42; Data = 'Test' } | Test-Object $Schema -ValidateOnly | Should -BeTrue
        @{ Data = 'Test' }          | Test-Object $Schema -ValidateOnly | Should -BeFalse
        @{ Id = 42; Test = 'Test' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
    }

    Context 'Required nodes formula' {

        it 'Not' {
            $Schema = @{
                a = @{ '@Type' = 'Int' }
                '@RequiredNodes' = 'not a'
            }
            @{ a = 1 }   | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1 }   | Test-Object $Schema | Should -not -BeNullOrEmpty
            @{ a = '1' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = '1' } | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        it 'And' {
            $Schema = @{
                a = @{ '@Type' = 'Int' }
                b = @{ '@Type' = 'Int' }
                '@RequiredNodes' = 'a and b'
            }
            @{ a = 1 }          | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1 }          | Test-Object $Schema | Should -not -BeNullOrEmpty
            @{ b = 2 }          | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ b = 2 }          | Test-Object $Schema | Should -not -BeNullOrEmpty
            @{ a = 1; b = 2 }   | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ a = 1; b = 2 }   | Test-Object $Schema | Should -BeNullOrEmpty
            @{ a = 1; b = '2' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = '2' } | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        it 'Or' {
            $Schema = @{
                a = @{ '@Type' = 'Int' }
                b = @{ '@Type' = 'Int' }
                '@RequiredNodes' = 'a or b'
            }
            @{ a = 1 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ a = 1 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ b = 2 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ b = 2 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ a = 1; b = 2 }   | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ a = 1; b = 2 }   | Test-Object $Schema | Should -BeNullOrEmpty
            @{ a = 1; b = '2' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = '2' } | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        it 'Xor' {
            $Schema = @{
                a = @{ '@Type' = 'Int' }
                b = @{ '@Type' = 'Int' }
                '@RequiredNodes' = 'a xor b'
            }
            @{ a = 1 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ a = 1 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ b = 2 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ b = 2 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ a = 1; b = 2 }   | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = 2 }   | Test-Object $Schema | Should -not -BeNullOrEmpty
            @{ a = 1; b = '2' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = '2' } | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        it 'Rambling Xor' {
            $Schema = @{
                a = @{ '@Type' = 'Int' }
                b = @{ '@Type' = 'Int' }
                '@RequiredNodes' = '(a and not b) or (not a and b)'
            }
            @{ a = 1 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ a = 1 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ b = 2 }          | Test-Object $Schema -ValidateOnly | Should -BeTrue
            @{ b = 2 }          | Test-Object $Schema | Should -BeNullOrEmpty
            @{ a = 1; b = 2 }   | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = 2 }   | Test-Object $Schema | Should -not -BeNullOrEmpty
            @{ a = 1; b = '2' } | Test-Object $Schema -ValidateOnly | Should -BeFalse
            @{ a = 1; b = '2' } | Test-Object $Schema | Should -not -BeNullOrEmpty
        }
    }

    Context 'Unique child nodes' {

        it 'Unique' {
            $Schema = @{
                '@Type' = [PSListNode]
                Children = @{'@Type' = [String]; '@Unique' = $true }
            }
            ,@('a', 'b', 'c') | Test-Object $Schema -ValidateOnly | Should -BeTrue
            ,@('a', 'b', 'c') | Test-Object $Schema | Should -BeNullOrEmpty
            ,@('a', 'b', 'a') | Test-Object $Schema -ValidateOnly | Should -BeFalse
            ,@('a', 'b', 'a') | Test-Object $Schema | Should -not -BeNullOrEmpty
        }

        it 'Unique collection' {
            $Schema = @{
                EnabledServers  = @(@{'@Type' = 'String'; '@Unique' = 'Server' })
                DisabledServers = @(@{'@Type' = 'String'; '@Unique' = 'Server' })
            }
            $Servers = @{
                EnabledServers  = 'NL1234', 'NL1235', 'NL1236'
                DisabledServers = 'NL1237', 'NL1238', 'NL1239'
            }
            $Servers | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Servers = @{
                EnabledServers  = 'NL1234', 'NL1235', 'NL1236'
                DisabledServers = 'NL1237', 'NL1235', 'NL1239'
            }
            $Servers | Test-Object $Schema -ValidateOnly | Should -BeFalse
            $Results = $Servers | Test-ObjectGraph $Schema
            $Results[0].Issue | Should -BeLike '*equal to the node*'
        }

        it 'Unique decedents' {
            $Schema = @{
                BookStore = @(
                    @{
                        Book = @{
                            Title = @{ '@Type' = 'String'; '@Unique' = 'Title' }
                            Price = @{ '@Type' = 'Double' }
                        }
                    }
                )
            }
            $Books = @{
                BookStore = @(
                    @{
                        Book = @{
                            Title = 'Harry Potter'
                            Price = 29.99
                        }
                    },
                    @{
                        Book = @{
                            Title = 'Learning PowerShell'
                            Price = 39.95
                        }
                    }
                )
            }
            $Books | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Books = @{
                BookStore = @(
                    @{
                        Book = @{
                            Title = 'Harry Potter'
                            Price = 29.99
                        }
                    },
                    @{
                        Book = @{
                            Title = 'Learning PowerShell'
                            Price = 39.95
                        }
                    },
                    @{
                        Book = @{
                            Title = 'Harry Potter'
                            Price = 24.99
                        }
                    }
                )
            }
            $Books | Test-Object $Schema -ValidateOnly | Should -BeFalse
        }
    }

    Context 'References' {

        BeforeAll {

            $Schema = @{
                '@Type' = [PSMapNode]
                '@References' = @{
                    Name = @{ '@Type' = 'String'; '@Match' = '\w{3,16}' }
                    Address = @{
                        '@Type' = [PSMapNode]
                        Street     = @{ '@Type' = 'String' }
                        City       = @{ '@Type' = 'String' }
                        State      = @{ '@Type' = 'String' }
                        PostalCode = @{ '@Type' = 'String' }
                    }
                }
                Buyer = @{
                    '@Type' = [PSMapNode]
                    FirstName = 'Name'
                    LastName  = 'Name'
                    ShippingAddress = 'Address'
                    BillingAddress  = 'Address'
                }
            }

            $RecurseSchema = @{
                '@References' = @{
                    Item = @{
                        '@AllowExtraNodes' = $true
                        Id = @{ '@Match' = '^ID\d{6}$'; '@Required' = $true }
                        Data = 'Item'
                    }
                }
                Test = 'Item'
            }
        }

        it '[V] Buyer' {

            $Data =
                @{
                    Buyer = @{
                        FirstName = 'John'
                        LastName  = 'Doe'
                        ShippingAddress = @{
                            Street     = '123 Main St'
                            City       = 'AnyTown'
                            State      = 'CA'
                            PostalCode = '12345'
                        }
                        BillingAddress  = @{
                            Street     = '456 Elm St'
                            City       = 'OtherTown'
                            State      = 'CA'
                            PostalCode = '67890'
                        }
                    }
                }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Data | Test-Object $Schema | Should -BeNullOrEmpty
        }

        it '[X] Buyer - incorrect LastName' {

            $Data =
                @{
                    Buyer = @{
                        FirstName = 'John'
                        LastName  = 'Do'  # Required 3-16 chars
                        ShippingAddress = @{
                            Street     = '123 Main St'
                            City       = 'AnyTown'
                            State      = 'CA'
                            PostalCode = '12345'
                        }
                        BillingAddress  = @{
                            Street     = '456 Elm St'
                            City       = 'OtherTown'
                            State      = 'CA'
                            PostalCode = '67890'
                        }
                    }
                }
            $Data | Test-Object $Schema -ValidateOnly | Should -BeFalse
            $Result = $Data | Test-Object $Schema
            $Result | Should -not -BeNullOrEmpty
            $Result.ObjectNode.Path  | Should -Contain 'Buyer.LastName'
            $Result.ObjectNode.Value | Should -Contain 'Do'
        }

        it '[V] Recursive reference' {
            $Data = @{
                Test = @{
                    Id = 'ID000001'
                    Data = @{
                        Id = 'ID000002'
                        Data = @{
                            Id = 'ID000003'
                        }
                    }
                }
            }

           $Data | Test-Object $RecurseSchema -ValidateOnly | Should -be $true
        }

        it '[V] Recursive object' -Skip:$($PSVersionTable.PSVersion -lt '6.0') {
            $Schema = @{
                '@References' = @{
                    RecursePSDrive = @{
                        '@AllowExtraNodes' = $true
                        Name     = @{ '@Type' = [String] }
                        Root     = @{ '@Type' = [String] }
                        Used     = @{ '@Type' = [Long] }
                        Provider = @{
                            '@AllowExtraNodes' = $true
                            Drives = @{
                                '@Type' = [PSListNode]
                                '@AllowExtraNodes' = $true
                                Drive = 'RecursePSDrive'
                            }
                        }
                    }
                }
                '@AllowExtraNodes' = $true
                Mode               = @{ '@Like' = '?????' }
                LastWriteTime      = @{ '@Type' = [DateTime] }
                Exists             = @{ '@Type' = [Bool] }
                Name               = @{ '@Type' = [String] }
                PSDrive            = 'RecursePSDrive'
            }

            Get-Item / | Test-Object $Schema -Depth 5 -ValidateOnly -WarningAction SilentlyContinue | Should -BeTrue
            $Result = Get-Item / | Test-Object $Schema -Depth 5 -Elaborate -WarningAction SilentlyContinue
            $Result.ObjectNode.Path | Should -Contain 'PSDrive.Provider.Drives[0].Name'
        }
    }

    Context 'Different Assert Test Prefix' {

        It "[V] AssertTestPrefix = '^'" {
            $Schema = @{
                AssertTestPrefix = '^'
                FirstName = @{ '^Type' = 'String' }
                LastName  = @{ '^Type' = 'String' }
                IsAlive   = @{ '^Type' = 'Bool' }
                Birthday  = @{ '^Type' = 'DateTime' }
                Age       = @{
                    '^Type' = 'Int'
                    '^Minimum' = 0
                    '^Maximum' = 99
                }
                Address = @{
                    '^Type' = 'PSMapNode'
                    Street     = @{ '^Type' = 'String' }
                    City       = @{ '^Type' = 'String' }
                    State      = @{ '^Type' = 'String' }
                    PostalCode = @{ '^Type' = 'String' }
                }
                Phone = @{
                    '^Type' = 'PSMapNode',  $Null
                    Home    = @{ '^Type' = 'String', 'PSListNode'; '^Match' = '^\d{3} \d{3}-\d{4}$'; '^AllowExtraNodes' = $true }
                    Mobile  = @{ '^Type' = 'String', 'PSListNode'; '^Match' = '^\d{3} \d{3}-\d{4}$'; '^AllowExtraNodes' = $true }
                    Work    = @{ '^Type' = 'String', 'PSListNode'; '^Match' = '^\d{3} \d{3}-\d{4}$'; '^AllowExtraNodes' = $true }
                }
                Children  = @(@{ '^Type' = 'String', $Null })
                Spouse    = @{ '^Type' = 'String', $Null }
            }
            $Person | Test-Object $Schema -ValidateOnly | Should -BeTrue
            $Person | Test-Object $Schema | Should -BeNullOrEmpty
        }

    }
}