File size: 8,062 Bytes
b8370f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import Foundation
import OpenClawKit
import OpenClawProtocol
import Testing

struct AnyCodableTests {
    @Test
    func `round trips epoch milliseconds beyond int 32`() throws {
        let epochMilliseconds: Int64 = 1_800_000_000_000
        let values = try self.representations([epochMilliseconds], json: "1800000000000")

        #expect(try String(decoding: JSONEncoder().encode(values[0]), as: UTF8.self) == "1800000000000")
        try self.expectRoundTrips(values, as: epochMilliseconds)
    }

    @Test(arguments: [Int64.min, -42, -1, 0, 1, 42, 9_007_199_254_740_993, Int64.max])
    func `round trips signed numbers from native and foundation`(_ value: Int64) throws {
        var native: [Any] = [value, NSNumber(value: value)]
        if let integer = Int(exactly: value) { native.append(integer) }
        let values = try self.representations(native, json: String(value))
        try self.expectRoundTrips(values, as: value)
    }

    @Test(arguments: [UInt64(Int64.max) + 1, UInt64(Int64.max) + 2, UInt64.max])
    func `round trips unsigned numbers beyond int 64`(_ value: UInt64) throws {
        let values = try self.representations([value, NSNumber(value: value)], json: String(value))
        try self.expectRoundTrips(values, as: value)
    }

    @Test(arguments: [0.0, 1.0, -0.5, 0.5, 1.5, Double(Int64.min), Double(Int64.max), Double(UInt64.max)])
    func `round trips floating numbers from native and foundation`(_ value: Double) throws {
        let values = try self.representations([value, NSNumber(value: value)], json: String(value))
        for wrapped in values {
            let data = try JSONEncoder().encode(wrapped)
            #expect(try JSONDecoder().decode(Double.self, from: data) == value)
        }
    }

    @Test
    func `boxed floating point values do not saturate integer bounds`() throws {
        let signedOverflow = try JSONEncoder().encode(AnyCodable(NSNumber(value: Double(Int64.max))))
        #expect(try JSONDecoder().decode(UInt64.self, from: signedOverflow) == UInt64(Int64.max) + 1)

        let unsignedOverflow = try JSONEncoder().encode(AnyCodable(NSNumber(value: Double(UInt64.max))))
        #expect(throws: DecodingError.self) {
            try JSONDecoder().decode(UInt64.self, from: unsignedOverflow)
        }
    }

    @Test
    func `preserves exact decimal integers from nested JSON serialization`() throws {
        let json = #"{"values":[9223372036854775807.0,18446744073709551615.0]}"#
        let foundation = try JSONSerialization.jsonObject(with: Data(json.utf8))
        let data = try JSONEncoder().encode(AnyCodable(foundation))
        let decoded = try JSONDecoder().decode([String: [UInt64]].self, from: data)
        #expect(decoded["values"] == [UInt64(Int64.max), UInt64.max])
    }

    @Test(arguments: [false, true])
    func `round trips booleans without equating them to numbers`(_ value: Bool) throws {
        let booleans = try self.representations([value, NSNumber(value: value)], json: value ? "true" : "false")
        try self.expectRoundTrips(booleans, as: value)

        let number = value ? 1 : 0
        let numbers = try self.representations(
            [number, NSNumber(value: number), Double(number), NSNumber(value: Double(number))],
            json: String(number))
        for numeric in numbers {
            #expect(numeric.boolValue == nil)
        }
        for boolean in booleans {
            #expect(boolean.boolValue == value)
            for numeric in numbers {
                #expect(boolean != numeric)
                #expect(numeric != boolean)
                #expect(Set([boolean, numeric]).count == 2)
                #expect(Set([numeric, boolean]).count == 2)
            }
        }
    }

    @Test
    func `round trips nested mixed payloads from native and foundation`() throws {
        struct Row: Decodable, Equatable {
            let signed: Int64
            let unsigned: UInt64
            let enabled: Bool
            let fraction: Double

            init(from decoder: Decoder) throws {
                var row = try decoder.unkeyedContainer()
                self.signed = try row.decode(Int64.self)
                self.unsigned = try row.decode(UInt64.self)
                self.enabled = try row.decode(Bool.self)
                self.fraction = try row.decode(Double.self)
            }
        }

        struct Payload: Decodable, Equatable {
            let generation: UInt64
            let enabled: Bool
            let nested: [String: [Row]]
        }

        let json = """
        {"generation":1,"enabled":true,"nested":{"rows":[
          [-42,1,false,0.5],
          [0,42,true,1.5]
        ]}}
        """
        let native: [String: Any] = [
            "generation": 1,
            "enabled": true,
            "nested": ["rows": [
                [Int64(-42), 1, false, 0.5] as [Any],
                [Int64(0), 42, true, 1.5] as [Any],
            ]],
        ]
        let foundation = NSDictionary(dictionary: [
            "generation": NSNumber(value: 1),
            "enabled": NSNumber(value: true),
            "nested": NSDictionary(dictionary: ["rows": NSArray(array: [
                NSArray(array: [
                    NSNumber(value: -42), NSNumber(value: 1),
                    NSNumber(value: false), NSNumber(value: 0.5),
                ]),
                NSArray(array: [
                    NSNumber(value: 0), NSNumber(value: 42),
                    NSNumber(value: true), NSNumber(value: 1.5),
                ]),
            ])]),
        ])
        let expected = try JSONDecoder().decode(Payload.self, from: Data(json.utf8))
        let values = try self.representations([native, foundation], json: json)

        try self.expectRoundTrips(values, as: expected)
    }

    private func representations(_ values: [Any], json: String) throws -> [AnyCodable] {
        let data = Data(json.utf8)
        return try values.map(AnyCodable.init) + [
            AnyCodable(JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])),
            JSONDecoder().decode(AnyCodable.self, from: data),
        ]
    }

    private func expectRoundTrips<Value: Decodable & Equatable>(
        _ values: [AnyCodable],
        as expected: Value,
        sourceLocation: SourceLocation = #_sourceLocation) throws
    {
        for lhs in values {
            for rhs in values {
                #expect(lhs == rhs, sourceLocation: sourceLocation)
            }
        }
        #expect(Set(values).count == 1, sourceLocation: sourceLocation)
        #expect(Set(values.reversed()).count == 1, sourceLocation: sourceLocation)

        for value in values {
            let data = try JSONEncoder().encode(value)
            #expect(try JSONDecoder().decode(Value.self, from: data) == expected, sourceLocation: sourceLocation)

            let decoded = try JSONDecoder().decode(AnyCodable.self, from: data)
            #expect(value == decoded, sourceLocation: sourceLocation)
            #expect(decoded == value, sourceLocation: sourceLocation)
            #expect(Set([value, decoded]).count == 1, sourceLocation: sourceLocation)

            let reencoded = try JSONEncoder().encode(decoded)
            #expect(try JSONDecoder().decode(Value.self, from: reencoded) == expected, sourceLocation: sourceLocation)
        }
    }
}

struct AnyCodableIntegerAccessTests {
    @Test
    func `integer access accepts only exact in-range values`() {
        let cases: [(Any, Int?)] = [
            (0, 0), (1, 1), (-1.0, -1), (-0.0, 0), (1.5, nil),
            (Int64.min, Int(exactly: Int64.min)),
            (Int64.max, Int(exactly: Int64.max)),
            (UInt64(Int.max) + 1, nil),
            (NSNumber(value: UInt64(Int.max) + 1), nil),
            (Double(Int.max) + 1, nil),
            (NSNumber(value: Double(Int.max) + 1), nil),
            (UInt64.max, nil), (Double.infinity, nil), (true, nil), (false, nil),
        ]
        for (value, expected) in cases {
            #expect(AnyCodable(value).intValue == expected)
        }
    }
}