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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
"""
Tests for all examples mentioned in the README.
These tests ensure that the documentation examples actually work.

⚠️  IMPORTANT REMINDER FOR AI ASSISTANTS:
When you modify any test in this file, you MUST also check and update the corresponding 
examples in /home/philip/kot/netencode/README.md to ensure they match the test requirements.

If a test expects 't5:hello,' (with comma), the README must show 't5:hello,' (with comma).
If a test expects complete netencode format, the README examples must be complete.

DO NOT just fix tests - always verify and update README examples to match!
"""
import pytest
import subprocess
from conftest import run_tool, get_tool_path
import netencode as ne


class TestReadmeExamples:
    """Test all the examples shown in the README."""
    
    def test_binary_data_construction_with_printf(self):
        """Test the exact example from README: Create filename with non-UTF-8 bytes."""
        # Create filename with non-UTF-8 bytes using shell
        cmd = ['bash', '-c', 'FILENAME=$(printf "file\\xff\\x00name"); LENGTH=$(printf "%s" "$FILENAME" | wc -c); echo $LENGTH']
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        # Should get correct length (9 bytes)
        length = int(result.stdout.strip())
        assert length == 9
        
        # Create netencode binary string
        cmd2 = ['bash', '-c', 'FILENAME=$(printf "file\\xff\\x00name"); LENGTH=$(printf "%s" "$FILENAME" | wc -c); printf "b${LENGTH}:${FILENAME},"']
        result2 = subprocess.run(cmd2, capture_output=True)
        
        # Should start with correct prefix and have correct total length
        assert result2.stdout.startswith(b'b9:')
        assert len(result2.stdout) == 13  # b9: + 9 bytes + ,
    
    def test_simple_printf_construction(self):
        """Test the printf construction example from README."""
        text = "Hello, World!"
        length = len(text.encode('utf-8'))
        result_format = f"b{length}:{text},"
        
        # Should create valid netencode binary string
        assert result_format == "b13:Hello, World!,"
    
    def test_basic_record_field_extraction(self):
        """Test extracting name field from record."""
        record = ne.simple_record(name=ne.text("Alice"), age=ne.integer(30))
        result = run_tool('netencode-record-get', 'name', stdin=record)
        
        # Should extract name field
        assert result.stdout.strip() == b't5:Alice,'
    
    def test_basic_record_field_extraction_with_plain(self):
        """Test extracting name field and converting to plain text - README example."""
        # Test the exact example: echo '{...}' | netencode-record-get name | netencode-plain
        record = ne.simple_record(name=ne.text("Alice"), age=ne.integer(30))
        field_result = run_tool('netencode-record-get', 'name', stdin=record)
        plain_result = run_tool('netencode-plain', stdin=field_result.stdout)
        
        # Should extract name as plain text
        assert plain_result.stdout == b'Alice'
    
    def test_configuration_extraction_with_plain(self):
        """Test configuration example from README with netencode-plain."""
        # README example: Extract HOST and PORT from config
        config = '{55:<4:host|t9:localhost,<4:port|n:8080,<5:debug|<4:true|u,}'
        
        # Extract host and convert to plain
        host_field = run_tool('netencode-record-get', 'host', stdin=config)
        host_plain = run_tool('netencode-plain', stdin=host_field.stdout)
        assert host_plain.stdout == b'localhost'
        
        # Extract port and convert to plain  
        port_field = run_tool('netencode-record-get', 'port', stdin=config)
        port_plain = run_tool('netencode-plain', stdin=port_field.stdout)
        assert port_plain.stdout == b'8080'
    
    def test_environment_integration_with_record_splice_env(self):
        """Test the echo example from README."""
        record = ne.simple_record(name=ne.text("Alice"), age=ne.integer(30))
        # Use shell -c to properly expand variables
        result = run_tool('netencode-to-env', 'sh', '-c', 'echo "Hello $name, you are $age years old"', 
                         stdin=record)
        
        # Should expand environment variables from record  
        assert result.stdout.strip() == b'Hello Alice, you are 30 years old'
    
    def test_env_splice_record_basic_functionality(self):
        """Test that env-to-netencode produces valid netencode."""
        # Set some test environment variables
        env = {'TEST_VAR1': 'test_value', 'TEST_VAR2': 'another_value'}
        
        # Run env-to-netencode with custom environment
        from conftest import get_tool_path
        tool_path = get_tool_path('env-to-netencode')
        result = subprocess.run(
            [tool_path],
            capture_output=True, text=True, env={**env}
        )
        
        # Should be a valid netencode record (starts with { and length)
        assert result.stdout.startswith('{')
        assert ':<' in result.stdout  # Should contain tagged fields
        # Should contain our test variables
        assert 'TEST_VAR1' in result.stdout
        assert 'test_value' in result.stdout
    
    def test_json_escaping_vs_netencode_simplicity(self):
        """Test that netencode handles quotes without escaping."""
        text = 'He said "Hello"'
        length = len(text.encode('utf-8'))
        result_format = f't{length}:{text},'
        
        # Should contain quotes literally (no escaping)
        assert result_format == 't15:He said "Hello",'
    
    def test_netencode_construction_from_shell_primitives(self):
        """Test that we can construct netencode using only printf and wc."""
        name = "Alice"
        age = "30"
        active = "true"
        
        # Construct individual fields (simplified version)
        name_field = f'<4:name|t{len(name)}:{name},'
        age_field = f'<3:age|n:{age},'
        active_field = f'<6:active|<{len(active)}:{active}|u,'
        
        # Calculate total length
        content = name_field + age_field + active_field
        total_length = len(content)
        
        # Construct record
        record = f'{{{total_length}:{content}}}'
        
        # Should be parseable by netencode-record-get
        name_result = run_tool('netencode-record-get', 'name', stdin=record)
        assert name_result.stdout.strip() == f't{len(name)}:{name},'.encode('utf-8')
        
        age_result = run_tool('netencode-record-get', 'age', stdin=record)
        assert age_result.stdout.strip() == f'n:{age},'.encode('utf-8')
    
    def test_pipeline_data_transformation(self):
        """Test complex pipeline similar to README example."""
        # Create test records
        record1 = '{"name": "Alice", "email": "alice@example.com", "status": "active"}'
        record2 = '{"name": "Bob", "email": "bob@example.com", "status": "inactive"}'
        
        # Convert to netencode
        ne1 = run_tool('json-to-netencode', stdin=record1).stdout.strip()
        ne2 = run_tool('json-to-netencode', stdin=record2).stdout.strip()
        input_stream = ne1 + b'\n' + ne2
        
        # Filter active records
        filtered = run_tool('netencode-filter', 'status=active', stdin=input_stream)
        
        # Should only contain Alice's record
        assert b'Alice' in filtered.stdout
        assert b'Bob' not in filtered.stdout
        
        # Extract fields using netencode-record-get
        name_result = run_tool('netencode-record-get', 'name', stdin=filtered.stdout.strip())
        email_result = run_tool('netencode-record-get', 'email', stdin=filtered.stdout.strip())
        
        # Extract the actual values (remove netencode formatting)
        name = name_result.stdout.strip()
        email = email_result.stdout.strip()
        
        # Should extract correct values in netencode format
        assert b'Alice' in name
        assert b'alice@example.com' in email
    
    def test_complete_pipeline_with_plain_output(self):
        """Test the complete pipeline example from README CLI Tools section."""
        # Use individual records as input (like streaming from an API) rather than an array
        alice_json = '{"name": "Alice", "active": true}'
        bob_json = '{"name": "Bob", "active": false}'
        
        # Convert each to netencode
        alice_ne = run_tool('json-to-netencode', stdin=alice_json).stdout.strip()
        bob_ne = run_tool('json-to-netencode', stdin=bob_json).stdout.strip()
        input_stream = alice_ne + b'\n' + bob_ne
        
        # Filter active users  
        filtered_data = run_tool('netencode-filter', 'active=true', stdin=input_stream)
        
        # Extract name field from filtered result
        name_field = run_tool('netencode-record-get', 'name', stdin=filtered_data.stdout.strip())
        
        # Convert to plain text
        plain_name = run_tool('netencode-plain', stdin=name_field.stdout)
        
        # Should get Alice as plain text
        assert plain_name.stdout == b'Alice'
    
    def test_type_safety_naturals_vs_integers(self):
        """Test that netencode distinguishes number types correctly."""
        natural = '{"count": 42}'
        integer = '{"temperature": -10}'
        
        natural_ne = run_tool('json-to-netencode', stdin=natural).stdout.strip()
        integer_ne = run_tool('json-to-netencode', stdin=integer).stdout.strip()
        
        # Extract the values
        count_result = run_tool('netencode-record-get', 'count', stdin=natural_ne).stdout.strip()
        temp_result = run_tool('netencode-record-get', 'temperature', stdin=integer_ne).stdout.strip()
        
        # Should be properly typed (JSON-to-netencode uses i: for all numbers)
        assert count_result == b'i:42,'
        assert temp_result == b'i:-10,'
    
    def test_length_prefixed_streaming_advantage(self):
        """Test that we can skip over data without parsing it."""
        record1 = ne.simple_record(name=ne.text("Alice"), age=ne.integer(30))
        record2 = ne.simple_record(name=ne.text("Bob"), age=ne.integer(25))
        
        # Should be able to extract fields from each record
        first_name = run_tool('netencode-record-get', 'name', stdin=record1).stdout.strip()
        second_name = run_tool('netencode-record-get', 'name', stdin=record2).stdout.strip()
        
        assert first_name == b't5:Alice,'
        assert second_name == b't3:Bob,'
    
    def test_boolean_representation_as_tagged_units(self):
        """Test boolean conversion to tagged units."""
        bool_record = '{"active": true, "disabled": false}'
        result = run_tool('json-to-netencode', stdin=bool_record)
        
        # Should convert to tagged units
        assert b'<6:active|<4:true|u,' in result.stdout
        assert b'<8:disabled|<5:false|u,' in result.stdout
        
        # Should be extractable
        active_result = run_tool('netencode-record-get', 'active', stdin=result.stdout.strip())
        assert active_result.stdout.strip() == b'<4:true|u,'
    
    
    def test_github_api_processing_simplified(self):
        """Test a simplified version that matches what our tools can actually do."""
        # Create mock GitHub API response for a single repo
        mock_repo = '{"name": "Hello-World", "archived": false, "description": "My first repository"}'
        
        # Convert to netencode
        netencode_repo = run_tool('json-to-netencode', stdin=mock_repo).stdout.strip()
        
        # Extract repository name
        repo_name = run_tool('netencode-record-get', 'name', stdin=netencode_repo).stdout.strip()
        assert repo_name == b't11:Hello-World,'
        
        # Check if archived field can be extracted
        archived_status = run_tool('netencode-record-get', 'archived', stdin=netencode_repo).stdout.strip()
        assert archived_status == b'<5:false|u,'
    
    def test_hello_netencode_json_conversion(self):
        """Test the Hello World JSON to netencode conversion."""
        json_data = '{"message": "Hello, World!"}'
        
        result = run_tool('json-to-netencode', stdin=json_data)
        assert result.returncode == 0
        assert result.stdout.strip() == b'{29:<7:message|t13:Hello, World!,}'
    
    def test_hello_netencode_pretty_print(self):
        """Test pretty printing the complex example from Hello Netencode section."""
        json_data = '{"name": "Alice", "age": 30, "active": true}'
        
        # Convert and pretty print
        netencode_result = run_tool('json-to-netencode', stdin=json_data)
        pretty_result = run_tool('netencode-pretty', stdin=netencode_result.stdout)
        
        assert pretty_result.returncode == 0
        assert b'Alice' in pretty_result.stdout
        assert b'active' in pretty_result.stdout
        assert b'age' in pretty_result.stdout
    
    def test_hello_netencode_full_pipeline(self):
        """Test the complete field extraction pipeline from Hello Netencode section."""
        json_data = '{"name": "Alice", "age": 30, "active": true}'
        
        # Full pipeline: JSON -> netencode -> record-get -> plain
        netencode_result = run_tool('json-to-netencode', stdin=json_data)
        get_result = run_tool('netencode-record-get', 'name', stdin=netencode_result.stdout)
        plain_result = run_tool('netencode-plain', stdin=get_result.stdout)
        
        assert plain_result.returncode == 0
        assert plain_result.stdout == b'Alice'
    
    def test_basic_user_record_creation(self):
        """Test creating a user record from JSON."""
        json_data = '{"name": "Alice", "age": 30, "active": true}'
        
        result = run_tool('json-to-netencode', stdin=json_data)
        assert result.returncode == 0
        assert result.stdout.strip() == b'{49:<6:active|<4:true|u,<3:age|i:30,<4:name|t5:Alice,}'
    
    def test_basic_field_extraction(self):
        """Test extracting name field from user record."""
        json_data = '{"name": "Alice", "age": 30}'
        
        # Convert to netencode and extract name
        netencode_result = run_tool('json-to-netencode', stdin=json_data)
        get_result = run_tool('netencode-record-get', 'name', stdin=netencode_result.stdout)
        plain_result = run_tool('netencode-plain', stdin=get_result.stdout)
        
        assert plain_result.returncode == 0
        assert plain_result.stdout == b'Alice'
    
    def test_filter_multiple_records(self):
        """Test filtering multiple user records."""
        # Create test data
        alice = '{"name": "Alice", "active": true}'
        bob = '{"name": "Bob", "active": false}'
        
        # Convert both to netencode
        alice_ne = run_tool('json-to-netencode', stdin=alice).stdout
        bob_ne = run_tool('json-to-netencode', stdin=bob).stdout
        
        # Combine records
        combined = alice_ne + bob_ne
        
        # Filter active users
        filter_result = run_tool('netencode-filter', 'active=true', stdin=combined)
        
        assert filter_result.returncode == 0
        assert b'Alice' in filter_result.stdout
        assert b'Bob' not in filter_result.stdout
    
    def test_python_user_record_creation(self):
        """Test the Python example for creating a user record."""
        # This tests that the Python code shown in the README actually works
        import netencode as ne
        
        user = ne.record([
            ("name", ne.text("Alice")),
            ("age", ne.integer(30)),
            ("active", ne.boolean(True))
        ])
        
        assert user == b'{49:<4:name|t5:Alice,<3:age|i:30,<6:active|<4:true|u,}'
    
    def test_python_nested_structure(self):
        """Test the Python example for creating nested structures."""
        import netencode as ne
        
        config = ne.record([
            ("database", ne.record([
                ("host", ne.text("localhost")),
                ("port", ne.integer(5432))
            ])),
            ("debug", ne.boolean(False))
        ])
        
        # Verify it creates valid netencode
        assert config.startswith(b'{')
        assert b'database' in config
        assert b'localhost' in config
        assert b'5432' in config
        assert b'<5:false|u,' in config