### ----------------------------------------------------
### -- BSON::Simple
### -- Licenses: Artistic-2.0
### -- Authors: Geoffrey Broadwell
### -- File: t/02-special.rakutest
### ----------------------------------------------------
### -- Chunk 1 of 2
use Test;
use lib $*PROGRAM.sibling('lib');
use CodecMatches;

use BSON::Simple;


plan 101;


# Adapted from the official BSON test corpus at:
# https://github.com/mongodb/specifications/tree/master/source/bson-corpus/tests


### Special/structural values

# binary

matches ⦃ x => BSON::Simple::Binary.new(0, :hex('')) ⦄,
        '0D000000057800000000000000';

matches ⦃ x => BSON::Simple::Binary.new(0, :base64('')) ⦄,
        '0D000000057800000000000000';

matches ⦃ x => BSON::Simple::Binary.new(0, :hex('FFFF')) ⦄,
        '0F0000000578000200000000FFFF00';

matches ⦃ x => BSON::Simple::Binary.new(0, :base64('//8=')) ⦄,
        '0F0000000578000200000000FFFF00';

matches ⦃ x => BSON::Simple::Binary.new(1, :base64('//8=')) ⦄,
        '0F0000000578000200000001FFFF00';

matches ⦃ x => BSON::Simple::Binary.new(2, :base64('//8=')) ⦄,
        '13000000057800060000000202000000FFFF00';

matches ⦃ x => BSON::Simple::Binary.new(3, :base64('c//SZESzTGmQ6OfR38A11A==')) ⦄,
        '1D000000057800100000000373FFD26444B34C6990E8E7D1DFC035D400';

matches ⦃ x => BSON::Simple::Binary.new(4, :base64('c//SZESzTGmQ6OfR38A11A==')) ⦄,
        '1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400';

matches ⦃ x => BSON::Simple::Binary.new(5, :base64('c//SZESzTGmQ6OfR38A11A==')) ⦄,
        '1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400';

matches ⦃ x => BSON::Simple::Binary.new(0x80, :base64('//8=')) ⦄,
        '0F0000000578000200000080FFFF00';

matches ⦃ x => ⦃ '$type' => 'string' ⦄ ⦄,
        '1F000000037800170000000224747970650007000000737472696E67000000';

matches ⦃ x => ⦃ '$type' => 2 ⦄ ⦄,
        '180000000378001000000010247479706500020000000000';

fails-decode '1D000000057800FF0000000573FFD26444B34C6990E8E7D1DFC035D400',
             'Length longer than document';

fails-decode '0D000000057800FFFFFFFF0000',
             'Negative length';

fails-decode '13000000057800060000000203000000FFFF00',
             'subtype 0x02 length too long';

fails-decode '13000000057800060000000201000000FFFF00',
             'subtype 0x02 length too short';

fails-decode '130000000578000600000002FFFFFFFFFFFF00',
             'subtype 0x02 length negative one';


# code
# NOTE: The original corpus tests are partially incorrect; see:
#           https://github.com/mongodb/specifications/issues/1040
#       The 10th hex digit of each BSON string has been changed to 'D' to fix this.

matches ⦃ a => BSON::Simple::JSCode.new(code => '') ⦄,
        '0D0000000D6100010000000000';

matches ⦃ a => BSON::Simple::JSCode.new(code => 'b') ⦄,
        '0E0000000D610002000000620000';

matches ⦃ a => BSON::Simple::JSCode.new(code => 'abababababab') ⦄,
        '190000000D61000D0000006162616261626162616261620000';

matches ⦃ a => BSON::Simple::JSCode.new(code => 'éééééé') ⦄,
        '190000000D61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000';

matches ⦃ a => BSON::Simple::JSCode.new(code => '☆☆☆☆') ⦄,
        '190000000D61000D000000E29886E29886E29886E298860000';

matches ⦃ a => BSON::Simple::JSCode.new(code => "ab\0bab\0babab") ⦄,
        '190000000D61000D0000006162006261620062616261620000';

fails-decode '0C0000000D61000000000000',
             'bad code string length: 0 (but no 0x00 either)';

fails-decode '0C0000000D6100FFFFFFFF00',
             'bad code string length: -1';

fails-decode '100000000D6100050000006200620000',
             'bad code string length: eats terminator';

fails-decode '120000000D00FFFFFF00666F6F6261720000',
             'bad code string length: longer than rest of document';

fails-decode '100000000D610004000000616263FF00',
             'code string is not null-terminated';

fails-decode '0E0000000D610001000000000000',
             'empty code string, but extra null';

fails-decode '0E0000000D610002000000E90000',
             'invalid UTF-8';


# dbref

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e') ⦄ ⦄,
        '37000000036462726566002b0000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0000';

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e'), '$db' => 'db' ⦄ ⦄,
        '4300000003646272656600370000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0224646200030000006462000000';

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => 42, '$db' => 'db', foo => 'bar' ⦄ ⦄,
        '48000000036462726566003c0000000224726566000b000000636f6c6c656374696f6e0010246964002a00000002246462000300000064620002666f6f0004000000626172000000';

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e'), foo => 'bar' ⦄ ⦄,
        '4400000003646272656600380000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e02666f6f0004000000626172000000';

matches ⦃ '$ref' => 'not-a-dbref', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e'), '$banana' => 'peel' ⦄,
        '3e0000000224726566000c0000006e6f742d612d646272656600072469640058921b3e6e32ab156a22b59e022462616e616e6100050000007065656c0000';

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e'), 'a.b' => 1, '$c' => 1 ⦄ ⦄,
        '48000000036462726566003c0000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e10612e62000100000010246300010000000000';

matches ⦃ dbref => ⦃ '$ref' => 'collection' ⦄ ⦄,
        '26000000036462726566001a0000000224726566000b000000636f6c6c656374696f6e000000';

matches ⦃ dbref => ⦃ '$ref' => 1, '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e') ⦄ ⦄,
        '2c000000036462726566002000000010247265660001000000072469640058921b3e6e32ab156a22b59e0000';

matches ⦃ dbref => ⦃ '$ref' => 'collection', '$id' => BSON::Simple::ObjectID.new('58921b3e6e32ab156a22b59e'), '$db' => 1 ⦄ ⦄,
        '4000000003646272656600340000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e1024646200010000000000';


# maxkey

matches ⦃ a => MaxKey ⦄, '080000007F610000';


# minkey

matches ⦃ a => MinKey ⦄, '08000000FF610000';


# oid

matches ⦃ a => BSON::Simple::ObjectID.new('000000000000000000000000') ⦄,
        '1400000007610000000000000000000000000000';

matches ⦃ a => BSON::Simple::ObjectID.new('ffffffffffffffffffffffff') ⦄,
        '14000000076100FFFFFFFFFFFFFFFFFFFFFFFF00';

matches ⦃ a => BSON::Simple::ObjectID.new('56e1fc72e0c917e9c4714161') ⦄,
        '1400000007610056E1FC72E0C917E9C471416100';

fails-decode '1200000007610056E1FC72E0C917E9C471', 'OID truncated';


# regex

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => '', options => '') ⦄,
        '0A0000000B6100000000';

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'abc', options => '') ⦄,
        '0D0000000B6100616263000000';

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'abc', options => 'im') ⦄,
        '0F0000000B610061626300696D0000';

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'ab/cd', options => 'im') ⦄,
        '110000000B610061622F636400696D0000';

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'abc', options => 'imx') ⦄,
        '100000000B610061626300696D780000';

encodes-to ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'abc', options => 'mix') ⦄,
           '100000000B610061626300696D780000';

decodes-to ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'abc', options => 'imx') ⦄,
           '100000000B6100616263006D69780000';

matches ⦃ a => BSON::Simple::PCRE_Regex.new(pattern => 'ab\\"ab', options => '') ⦄,
        '100000000B610061625C226162000000';

matches ⦃ '$regex' => BSON::Simple::PCRE_Regex.new(pattern => 'pattern', options => 'ix') ⦄,
        '180000000B247265676578007061747465726E0069780000';

matches ⦃ '$regex' => BSON::Simple::PCRE_Regex.new(pattern => 'pattern', options => ''), '$options' => 'ix' ⦄,
        '270000000B247265676578007061747465726E000002246F7074696F6E73000300000069780000';

fails-decode '0F0000000B610061006300696D0000', 'embedded null in pattern';

fails-decode '100000000B61006162630069006D0000', 'embedded null in flags';


# timestamp

matches ⦃ a => BSON::Simple::Timestamp.new(t => 123456789, i => 42) ⦄,
        '100000001161002A00000015CD5B0700';

matches ⦃ a => BSON::Simple::Timestamp.new(t => 4294967295, i => 4294967295) ⦄,
        '10000000116100FFFFFFFFFFFFFFFF00';

matches ⦃ a => BSON::Simple::Timestamp.new(t => 4000000000, i => 4000000000) ⦄,
        '1000000011610000286BEE00286BEE00';

fails-decode '0f0000001161002A00000015CD5B00', 'Truncated timestamp field';


### Deprecated values

# code_w_scope

matches ⦃ a => BSON::Simple::ScopedJS.new(code => '', scope => {}) ⦄,
        '160000000F61000E0000000100000000050000000000';

matches ⦃ a => BSON::Simple::ScopedJS.new(code => 'abcd', scope => {}) ⦄,
        '1A0000000F610012000000050000006162636400050000000000';

matches ⦃ a => BSON::Simple::ScopedJS.new(code => '', scope => { x => 1 }) ⦄,
        '1D0000000F61001500000001000000000C000000107800010000000000';

matches ⦃ a => BSON::Simple::ScopedJS.new(code => 'abcd', scope => { x => 1 }) ⦄,
        '210000000F6100190000000500000061626364000C000000107800010000000000';

matches ⦃ a => BSON::Simple::ScopedJS.new(code => "é\0d", scope => {}) ⦄,
        '1A0000000F61001200000005000000C3A9006400050000000000';

fails-decode '280000000F6100000000000500000061626364001300000010780001000000107900010000000000',
'field length zero';

fails-decode '280000000F6100FFFFFFFF0500000061626364001300000010780001000000107900010000000000',
'field length negative';

fails-decode '160000000F61000D0000000100000000050000000000',
'field length too short (less than minimum size)';

fails-decode '280000000F61001F0000000500000061626364001300000010780001000000107900010000000000',
'field length too short (truncates scope)';

fails-decode '280000000F6100210000000500000061626364001300000010780001000000107900010000000000',
'field length too long (clips outer doc)';

fails-decode '280000000F6100FF0000000500000061626364001300000010780001000000107900010000000000',
'field length too long (longer than outer doc)';

fails-decode '280000000F6100200000000400000061626364001300000010780001000000107900010000000000',
'bad code string: length too short';

fails-decode '280000000F6100200000000600000061626364001300000010780001000000107900010000000000',
'bad code string: length too long (clips scope)';

fails-decode '280000000F610020000000FFFFFFFF61626364001300000010780001000000107900010000000000',
'bad code string: negative length';

fails-decode '280000000F610020000000FF00000061626364001300000010780001000000107900010000000000',
'bad code string: length longer than field';

fails-decode '1C0000000F001500000001000000000C000000020000000000000000',
'bad scope doc (field has bad string length)';


# dbpointer

my $oid = BSON::Simple::ObjectID.new('56e1fc72e0c917e9c4714161');
matches ⦃ a => BSON::Simple::DBPointer.new(ref => 'b', :$oid) ⦄,
        '1A0000000C610002000000620056E1FC72E0C917E9C471416100';

matches ⦃ a => BSON::Simple::DBPointer.new(ref => 'é', :$oid) ⦄,
        '1B0000000C610003000000C3A90056E1FC72E0C917E9C471416100';

fails-decode '1A0000000C6100FFFFFFFF620056E1FC72E0C917E9C471416100',
             'String with negative length';

fails-decode '1A0000000C610000000000620056E1FC72E0C917E9C471416100',
             'String with zero length';

fails-decode '1A0000000C610002000000626256E1FC72E0C917E9C471416100',
             'String not null terminated';

fails-decode '160000000C61000300000061620056E1FC72E0C91700',
             'short OID (less than minimum length for field)';

fails-decode '1A0000000C61000300000061620056E1FC72E0C917E9C4716100',
             'short OID (greater than minimum, but truncated)';

fails-decode '1A0000000C610002000000E90056E1FC72E0C917E9C471416100',
             'String with bad UTF-8';


# symbol

matches ⦃ a => BSON::Simple::Symbol('') ⦄,
        '0D0000000E6100010000000000';

matches ⦃ a => BSON::Simple::Symbol('b') ⦄,
        '0E0000000E610002000000620000';

matches ⦃ a => BSON::Simple::Symbol('abababababab') ⦄,
        '190000000E61000D0000006162616261626162616261620000';

matches ⦃ a => BSON::Simple::Symbol('éééééé') ⦄,
        '190000000E61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000';

matches ⦃ a => BSON::Simple::Symbol('☆☆☆☆') ⦄,
        '190000000E61000D000000E29886E29886E29886E298860000';

matches ⦃ a => BSON::Simple::Symbol("ab\0bab\0babab") ⦄,
        '190000000E61000D0000006162006261620062616261620000';

fails-decode '0C0000000E61000000000000',
             'bad symbol length: 0 (but no 0x00 either)';

fails-decode '0C0000000E6100FFFFFFFF00',
             'bad symbol length: -1';

fails-decode '100000000E6100050000006200620000',
             'bad symbol length: eats terminator';

fails-decode '120000000E00FFFFFF00666F6F6261720000',
             'bad symbol length: longer than rest of document';

fails-decode '100000000E610004000000616263FF00',
             'symbol is not null-terminated';

fails-decode '0E0000000E610001000000000000',
             'empty symbol, but extra null';

fails-decode '0E0000000E610002000000E90000',
             'invalid UTF-8';


# undefined

matches ⦃ a => Mu ⦄, '0800000006610000';


### Multi-type tests

# multi-type

matches ⦃
    _id               => BSON::Simple::ObjectID.new('57e193d7a9cc81b4027498b5'),
    String            => 'string',
    Int32             => 42,
    Int64             => BSON::Simple::Int64.new(42),
    Double            => -1e0,
    Binary            => BSON::Simple::Binary.new(3, base64 => 'o0w498Or7cijeBSpkquNtg=='),
    BinaryUserDefined => BSON::Simple::Binary.new(0x80, base64 => 'AQIDBAU='),
    Code              => BSON::Simple::JSCode.new(code => 'function() {}'),
    CodeWithScope     => BSON::Simple::ScopedJS.new(code => 'function() {}', scope => {}),
    Subdocument       => ⦃ foo => 'bar' ⦄,
    Array             => [1, 2, 3, 4, 5],
    Timestamp         => BSON::Simple::Timestamp.new(t => 42, i => 1),
    Regex             => BSON::Simple::PCRE_Regex.new(pattern => 'pattern', options => ''),
    DatetimeEpoch     => Instant.from-posix(0),
    DatetimePositive  => Instant.from-posix( 2147483.647),
    DatetimeNegative  => Instant.from-posix(-2147483.648),
    True              => True,
    False             => False,
    DBRef             => ⦃ '$ref' => 'collection',
                           '$id'  => BSON::Simple::ObjectID.new('57fd71e96e32ab4225b723fb'),
                           '$db'  => 'database' ⦄,
    Minkey            => MinKey,
    Maxkey            => MaxKey,
    Null              => Any,
⦄,
'F4010000075F69640057E193D7A9CC81B402