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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
"""
Test the netencode_py module itself.
"""
import pytest
import netencode as ne
from collections import OrderedDict


class TestBasicTypes:
    """Test basic netencode type constructors."""
    
    def test_unit(self):
        """Test unit value construction."""
        assert ne.unit() == b"u,"
    
    def test_natural_numbers(self):
        """Test natural number construction."""
        assert ne.natural(0) == b"n:0,"
        assert ne.natural(42) == b"n:42,"
        assert ne.natural(2**64 - 1) == b"n:18446744073709551615,"
        
        # Test error cases
        with pytest.raises(ValueError, match="Natural numbers must be non-negative"):
            ne.natural(-1)
        
        with pytest.raises(ValueError, match="Natural number too large"):
            ne.natural(2**64)
    
    def test_integers(self):
        """Test signed integer construction."""
        assert ne.integer(0) == b"i:0,"
        assert ne.integer(42) == b"i:42,"
        assert ne.integer(-42) == b"i:-42,"
        assert ne.integer(2**63 - 1) == b"i:9223372036854775807,"
        assert ne.integer(-(2**63)) == b"i:-9223372036854775808,"
        
        # Test error cases
        with pytest.raises(ValueError, match="Integer out of 64-bit signed range"):
            ne.integer(2**63)
        
        with pytest.raises(ValueError, match="Integer out of 64-bit signed range"):
            ne.integer(-(2**63) - 1)
    
    def test_booleans(self):
        """Test boolean construction."""
        assert ne.boolean(True) == b"<4:true|u,"
        assert ne.boolean(False) == b"<5:false|u,"
    
    def test_text(self):
        """Test text string construction."""
        assert ne.text("") == b"t0:,"
        assert ne.text("hello") == b"t5:hello,"
        assert ne.text("Hello, World!") == b"t13:Hello, World!,"
        
        # Test UTF-8 encoding
        assert ne.text("café") == b"t5:caf\xc3\xa9,"  # é is 2 bytes in UTF-8
        assert ne.text("🌍") == b"t4:\xf0\x9f\x8c\x8d,"      # emoji is 4 bytes in UTF-8
        
        # Test special characters
        assert ne.text("He said \"hi\"") == b"t12:He said \"hi\","
        assert ne.text("line1\nline2") == b"t11:line1\nline2,"
    
    def test_binary(self):
        """Test binary data construction."""
        assert ne.binary(b"") == b"b0:,"
        assert ne.binary(b"hello") == b"b5:hello,"
        assert ne.binary(b"\x00\x01\x02") == b"b3:\x00\x01\x02,"


class TestCompositeTypes:
    """Test composite type constructors."""
    
    def test_tag(self):
        """Test tagged value construction."""
        assert ne.tag("foo", b"u,") == b"<3:foo|u,"
        assert ne.tag("", b"i:42,") == b"<0:|i:42,"
        assert ne.tag("Some", b"t5:value,") == b"<4:Some|t5:value,"
        
        # Test UTF-8 tag names
        assert ne.tag("café", b"u,") == b"<5:caf\xc3\xa9|u,"
    
    def test_record_from_dict(self):
        """Test record construction from regular dict (sorted)."""
        # Regular dicts are sorted by key
        record = ne.record({"b": b"t1:2,", "a": b"t1:1,"})
        
        # Should be sorted alphabetically
        assert b"<1:a|t1:1," in record
        assert b"<1:b|t1:2," in record
        
        # Check ordering
        a_pos = record.find(b"<1:a|")
        b_pos = record.find(b"<1:b|")
        assert a_pos < b_pos
    
    def test_record_from_ordered_dict(self):
        """Test record construction from OrderedDict."""
        fields = OrderedDict()
        fields["zebra"] = b"t1:1,"
        fields["alpha"] = b"t1:2,"
        
        record = ne.record(fields)
        
        # Should preserve order from OrderedDict
        zebra_pos = record.find(b"<5:zebra|")
        alpha_pos = record.find(b"<5:alpha|")
        assert zebra_pos < alpha_pos
    
    def test_record_from_list(self):
        """Test record construction from list of tuples."""
        fields = [("third", b"t1:c,"), ("first", b"t1:a,"), ("second", b"t1:b,")]
        record = ne.record(fields)
        
        # Should preserve order from list
        third_pos = record.find(b"<5:third|")
        first_pos = record.find(b"<5:first|")
        second_pos = record.find(b"<6:second|")
        
        assert third_pos < first_pos < second_pos
    
    def test_record_length_calculation(self):
        """Test that record length is calculated correctly."""
        # Simple single field record
        record = ne.record([("a", b"u,")])
        # Content: <1:a|u, = 7 bytes
        assert record.startswith(b"{7:")
        
        # Multi-field record
        record = ne.record([("foo", b"i:42,"), ("bar", b"t3:baz,")])
        # Content: <3:foo|i:42,<3:bar|t3:baz, = 26 bytes
        assert record.startswith(b"{26:")
    
    def test_list(self):
        """Test list construction."""
        # Empty list
        empty_list = ne.list([])
        assert empty_list == b"[0:]"
        
        # Single item list
        single_list = ne.list([b"t5:hello,"])
        assert single_list == b"[9:t5:hello,]"
        
        # Multi-item list
        multi_list = ne.list([b"t3:foo,", b"i:42,", b"u,"])
        # Content: t3:foo,i:42,u, = 14 bytes
        assert multi_list == b"[14:t3:foo,i:42,u,]"
    
    def test_record_ordered(self):
        """Test record_ordered convenience function."""
        record = ne.record_ordered(
            "second", b"t1:2,",
            "first", b"t1:1,"
        )
        
        # Should maintain explicit order
        second_pos = record.find(b"<6:second|")
        first_pos = record.find(b"<5:first|")
        assert second_pos < first_pos
        
        # Test error case
        with pytest.raises(ValueError, match="even number of arguments"):
            ne.record_ordered("key1", "value1", "key2")
    
    def test_simple_record(self):
        """Test simple_record convenience function."""
        record = ne.simple_record(zebra=b"t1:1,", alpha=b"t1:2,")
        
        # Should be alphabetically sorted
        alpha_pos = record.find(b"<5:alpha|")
        zebra_pos = record.find(b"<5:zebra|")
        assert alpha_pos < zebra_pos


class TestComplexScenarios:
    """Test complex netencode construction scenarios."""
    
    def test_nested_structures(self):
        """Test nested records and lists."""
        # Create nested record containing a list
        inner_list = ne.list([ne.text("foo"), ne.text("bar")])
        record = ne.record([("items", inner_list)])
        
        assert b"<5:items|[" in record
        assert b"t3:foo," in record
        assert b"t3:bar," in record
    
    def test_all_types_in_record(self):
        """Test record containing all netencode types."""
        record = ne.record_ordered(
            "unit_field", ne.unit(),
            "nat_field", ne.natural(42),
            "int_field", ne.integer(-10),
            "bool_field", ne.boolean(True),
            "text_field", ne.text("hello"),
            "binary_field", ne.binary(b"data"),
            "tag_field", ne.tag("Some", ne.text("value")),
            "list_field", ne.list([ne.text("a"), ne.text("b")])
        )
        
        # Verify all field types are present
        assert b"<10:unit_field|u," in record
        assert b"<9:nat_field|n:42," in record
        assert b"<9:int_field|i:-10," in record
        assert b"<10:bool_field|<4:true|u," in record
        assert b"<10:text_field|t5:hello," in record
        assert b"<12:binary_field|b4:data," in record
        assert b"<9:tag_field|<4:Some|t5:value," in record
        assert b"<10:list_field|[" in record
    
    def test_field_ordering_edge_cases(self):
        """Test field ordering with edge cases."""
        # Fields with same prefix but different lengths
        record = ne.record_ordered(
            "a", ne.text("1"),
            "aa", ne.text("2"),
            "aaa", ne.text("3")
        )
        
        # Verify ordering is maintained
        a_pos = record.find(b"<1:a|t1:1,")
        aa_pos = record.find(b"<2:aa|t1:2,")
        aaa_pos = record.find(b"<3:aaa|t1:3,")
        
        assert a_pos < aa_pos < aaa_pos
    
    def test_unicode_edge_cases(self):
        """Test Unicode handling in various contexts."""
        # Unicode in field names
        record = ne.record([("café", ne.text("value"))])
        assert b"<5:caf\xc3\xa9|t5:value," in record
        
        # Unicode in text values
        text_val = ne.text("Hello 世界 🌍")
        assert b"t17:Hello \xe4\xb8\x96\xe7\x95\x8c \xf0\x9f\x8c\x8d," == text_val  # 17 bytes in UTF-8
        
        # Unicode in tags
        tagged = ne.tag("世界", ne.unit())
        assert b"<6:\xe4\xb8\x96\xe7\x95\x8c|u," == tagged  # 6 bytes for "世界" in UTF-8
    
    def test_large_structures(self):
        """Test handling of larger structures."""
        # Create a record with many fields
        fields = []
        for i in range(100):
            fields.append((f"field_{i:03d}", ne.natural(i)))
        
        record = ne.record(fields)
        
        # Should handle large structures correctly
        assert record.startswith(b"{")
        assert record.endswith(b"}")
        
        # Verify some fields are present
        assert b"<9:field_000|n:0," in record
        assert b"<9:field_099|n:99," in record


class TestErrorHandling:
    """Test error handling and validation."""
    
    def test_record_invalid_input_types(self):
        """Test record construction with invalid input types."""
        with pytest.raises(TypeError, match="fields must be dict, OrderedDict, or list"):
            ne.record("invalid")
        
        with pytest.raises(TypeError, match="fields must be dict, OrderedDict, or list"):
            ne.record(42)
    
    def test_text_encoding_edge_cases(self):
        """Test text encoding with edge cases."""
        # Empty string
        assert ne.text("") == b"t0:,"
        
        # String with null bytes (should work)
        assert ne.text("hello\x00world") == b"t11:hello\x00world,"
        
        # Very long string
        long_string = "a" * 1000
        result = ne.text(long_string)
        assert result == f"t1000:{long_string},".encode('utf-8')
    
    def test_binary_edge_cases(self):
        """Test binary data handling edge cases."""
        # Empty binary
        assert ne.binary(b"") == b"b0:,"
        
        # Binary with null bytes
        assert ne.binary(b"\x00\xff") == b"b2:\x00\xff,"
        
        # Large binary data
        large_data = b"x" * 1000
        result = ne.binary(large_data)
        assert result.startswith(b"b1000:")
        assert len(result) == 1007  # "b1000:" + data + ","