velocity-python 0.0.132__py3-none-any.whl → 0.0.135__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

Files changed (67) hide show
  1. velocity/__init__.py +1 -1
  2. velocity/app/tests/__init__.py +1 -0
  3. velocity/app/tests/test_email_processing.py +112 -0
  4. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  5. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  6. velocity/aws/tests/__init__.py +1 -0
  7. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  8. velocity/aws/tests/test_response.py +163 -0
  9. velocity/db/core/decorators.py +20 -3
  10. velocity/db/core/engine.py +33 -7
  11. velocity/db/exceptions.py +7 -0
  12. velocity/db/servers/base/initializer.py +2 -1
  13. velocity/db/servers/mysql/__init__.py +13 -4
  14. velocity/db/servers/postgres/__init__.py +14 -4
  15. velocity/db/servers/sqlite/__init__.py +13 -4
  16. velocity/db/servers/sqlserver/__init__.py +13 -4
  17. velocity/db/tests/__init__.py +1 -0
  18. velocity/db/tests/common_db_test.py +0 -0
  19. velocity/db/tests/postgres/__init__.py +1 -0
  20. velocity/db/tests/postgres/common.py +49 -0
  21. velocity/db/tests/postgres/test_column.py +29 -0
  22. velocity/db/tests/postgres/test_connections.py +25 -0
  23. velocity/db/tests/postgres/test_database.py +21 -0
  24. velocity/db/tests/postgres/test_engine.py +205 -0
  25. velocity/db/tests/postgres/test_general_usage.py +88 -0
  26. velocity/db/tests/postgres/test_imports.py +8 -0
  27. velocity/db/tests/postgres/test_result.py +19 -0
  28. velocity/db/tests/postgres/test_row.py +137 -0
  29. velocity/db/tests/postgres/test_row_comprehensive.py +707 -0
  30. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  31. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  32. velocity/db/tests/postgres/test_sequence.py +34 -0
  33. velocity/db/tests/postgres/test_sql_comprehensive.py +471 -0
  34. velocity/db/tests/postgres/test_table.py +101 -0
  35. velocity/db/tests/postgres/test_table_comprehensive.py +644 -0
  36. velocity/db/tests/postgres/test_transaction.py +106 -0
  37. velocity/db/tests/sql/__init__.py +1 -0
  38. velocity/db/tests/sql/common.py +177 -0
  39. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  40. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  41. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  42. velocity/db/tests/test_db_utils.py +221 -0
  43. velocity/db/tests/test_postgres.py +212 -0
  44. velocity/db/tests/test_postgres_unchanged.py +81 -0
  45. velocity/db/tests/test_process_error_robustness.py +292 -0
  46. velocity/db/tests/test_result_caching.py +279 -0
  47. velocity/db/tests/test_result_sql_aware.py +117 -0
  48. velocity/db/tests/test_row_get_missing_column.py +72 -0
  49. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  50. velocity/db/tests/test_schema_locking_simple.py +97 -0
  51. velocity/db/tests/test_sql_builder.py +165 -0
  52. velocity/db/tests/test_tablehelper.py +486 -0
  53. velocity/misc/tests/__init__.py +1 -0
  54. velocity/misc/tests/test_db.py +90 -0
  55. velocity/misc/tests/test_fix.py +78 -0
  56. velocity/misc/tests/test_format.py +64 -0
  57. velocity/misc/tests/test_iconv.py +203 -0
  58. velocity/misc/tests/test_merge.py +82 -0
  59. velocity/misc/tests/test_oconv.py +144 -0
  60. velocity/misc/tests/test_original_error.py +52 -0
  61. velocity/misc/tests/test_timer.py +74 -0
  62. {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/METADATA +1 -1
  63. velocity_python-0.0.135.dist-info/RECORD +128 -0
  64. velocity_python-0.0.132.dist-info/RECORD +0 -76
  65. {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/WHEEL +0 -0
  66. {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/licenses/LICENSE +0 -0
  67. {velocity_python-0.0.132.dist-info → velocity_python-0.0.135.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,707 @@
1
+ import unittest
2
+ import threading
3
+ import time
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from velocity.db.exceptions import DbColumnMissingError, DbDuplicateKeyError
6
+ from velocity.db.core.row import Row
7
+ from .common import CommonPostgresTest, engine, test_db
8
+
9
+
10
+ @engine.transaction
11
+ class TestRowComprehensive(CommonPostgresTest):
12
+ """Comprehensive tests for Row class including edge cases, race conditions, and error recovery."""
13
+
14
+ @classmethod
15
+ def create_test_tables(cls, tx):
16
+ """Create comprehensive test tables for Row testing."""
17
+ # Basic table for row operations
18
+ tx.table("row_test_basic").create(
19
+ columns={
20
+ "name": str,
21
+ "age": int,
22
+ "email": str,
23
+ "active": bool,
24
+ "score": float,
25
+ "metadata": str,
26
+ "created_at": str,
27
+ }
28
+ )
29
+
30
+ # Table with complex data types
31
+ tx.table("row_test_complex").create(
32
+ columns={
33
+ "title": str,
34
+ "description": str,
35
+ "tags": str, # JSON-like string
36
+ "priority": int,
37
+ "deadline": str,
38
+ "completed": bool,
39
+ "progress": float,
40
+ }
41
+ )
42
+
43
+ # Table for constraint testing
44
+ tx.table("row_test_constraints").create(
45
+ columns={
46
+ "username": str,
47
+ "email": str,
48
+ "phone": str,
49
+ "status": str,
50
+ }
51
+ )
52
+
53
+ # Create unique constraint
54
+ try:
55
+ tx.table("row_test_constraints").create_index("username", unique=True)
56
+ tx.table("row_test_constraints").create_index("email", unique=True)
57
+ except:
58
+ pass
59
+
60
+ # Parent-child tables for relationship testing
61
+ tx.table("row_test_parent").create(
62
+ columns={
63
+ "parent_name": str,
64
+ "category": str,
65
+ "priority": int,
66
+ }
67
+ )
68
+
69
+ tx.table("row_test_child").create(
70
+ columns={
71
+ "parent_id": int,
72
+ "child_name": str,
73
+ "value": float,
74
+ "notes": str,
75
+ }
76
+ )
77
+
78
+ tx.table("row_test_child").create_foreign_key("parent_id", "row_test_parent", "sys_id")
79
+
80
+ # Table for concurrent testing
81
+ tx.table("row_test_concurrent").create(
82
+ columns={
83
+ "counter": int,
84
+ "worker_id": str,
85
+ "last_updated": str,
86
+ "version": int,
87
+ }
88
+ )
89
+
90
+ # Insert initial test data
91
+ cls.insert_test_data(tx)
92
+
93
+ @classmethod
94
+ def insert_test_data(cls, tx):
95
+ """Insert comprehensive test data."""
96
+ # Basic table data
97
+ basic_data = [
98
+ {
99
+ "name": "Alice Johnson",
100
+ "age": 30,
101
+ "email": "alice@test.com",
102
+ "active": True,
103
+ "score": 95.5,
104
+ "metadata": '{"role": "admin", "department": "IT"}',
105
+ "created_at": "2023-01-01T10:00:00"
106
+ },
107
+ {
108
+ "name": "Bob Smith",
109
+ "age": 25,
110
+ "email": "bob@test.com",
111
+ "active": False,
112
+ "score": 87.2,
113
+ "metadata": '{"role": "user", "department": "Sales"}',
114
+ "created_at": "2023-01-02T11:00:00"
115
+ },
116
+ {
117
+ "name": "Charlie Brown",
118
+ "age": 35,
119
+ "email": "charlie@test.com",
120
+ "active": True,
121
+ "score": 92.8,
122
+ "metadata": '{"role": "manager", "department": "HR"}',
123
+ "created_at": "2023-01-03T12:00:00"
124
+ },
125
+ ]
126
+
127
+ for data in basic_data:
128
+ tx.table("row_test_basic").insert(data)
129
+
130
+ # Complex table data
131
+ complex_data = [
132
+ {
133
+ "title": "Project Alpha",
134
+ "description": "Main development project",
135
+ "tags": '["development", "priority", "Q1"]',
136
+ "priority": 1,
137
+ "deadline": "2023-03-31",
138
+ "completed": False,
139
+ "progress": 75.5
140
+ },
141
+ {
142
+ "title": "Project Beta",
143
+ "description": "Testing and QA project",
144
+ "tags": '["testing", "qa", "Q2"]',
145
+ "priority": 2,
146
+ "deadline": "2023-06-30",
147
+ "completed": True,
148
+ "progress": 100.0
149
+ },
150
+ ]
151
+
152
+ for data in complex_data:
153
+ tx.table("row_test_complex").insert(data)
154
+
155
+ # Constraints table data
156
+ constraint_data = [
157
+ {"username": "admin", "email": "admin@test.com", "phone": "555-0001", "status": "active"},
158
+ {"username": "user1", "email": "user1@test.com", "phone": "555-0002", "status": "pending"},
159
+ {"username": "user2", "email": "user2@test.com", "phone": "555-0003", "status": "suspended"},
160
+ ]
161
+
162
+ for data in constraint_data:
163
+ tx.table("row_test_constraints").insert(data)
164
+
165
+ # Parent-child data
166
+ parent_data = [
167
+ {"parent_name": "Department A", "category": "Engineering", "priority": 1},
168
+ {"parent_name": "Department B", "category": "Marketing", "priority": 2},
169
+ ]
170
+
171
+ parent_ids = []
172
+ for data in parent_data:
173
+ row = tx.table("row_test_parent").new(data)
174
+ parent_ids.append(row["sys_id"])
175
+
176
+ child_data = [
177
+ {"parent_id": parent_ids[0], "child_name": "Team 1", "value": 100.0, "notes": "Primary team"},
178
+ {"parent_id": parent_ids[0], "child_name": "Team 2", "value": 85.5, "notes": "Secondary team"},
179
+ {"parent_id": parent_ids[1], "child_name": "Team 3", "value": 92.3, "notes": "Marketing team"},
180
+ ]
181
+
182
+ for data in child_data:
183
+ tx.table("row_test_child").insert(data)
184
+
185
+ # Concurrent testing data
186
+ for i in range(10):
187
+ tx.table("row_test_concurrent").insert({
188
+ "counter": i,
189
+ "worker_id": "initial",
190
+ "last_updated": "2023-01-01T00:00:00",
191
+ "version": 1
192
+ })
193
+
194
+ def test_row_initialization(self, tx):
195
+ """Test row initialization with different key types."""
196
+ table = tx.table("row_test_basic")
197
+
198
+ # Test initialization with sys_id
199
+ first_row_data = table.select().one()
200
+ sys_id = first_row_data["sys_id"]
201
+
202
+ row_by_id = table.row(sys_id)
203
+ self.assertIsInstance(row_by_id, Row)
204
+ self.assertEqual(row_by_id["sys_id"], sys_id)
205
+
206
+ # Test initialization with dictionary key
207
+ row_by_dict = table.row({"name": "Alice Johnson"})
208
+ self.assertIsInstance(row_by_dict, Row)
209
+ self.assertEqual(row_by_dict["name"], "Alice Johnson")
210
+
211
+ # Test initialization with complex dictionary key
212
+ row_by_complex = table.row({"name": "Alice Johnson", "email": "alice@test.com"})
213
+ self.assertIsInstance(row_by_complex, Row)
214
+ self.assertEqual(row_by_complex["name"], "Alice Johnson")
215
+
216
+ def test_row_representation(self, tx):
217
+ """Test row string and representation methods."""
218
+ table = tx.table("row_test_basic")
219
+ row = table.select().one()
220
+
221
+ # Test __repr__
222
+ repr_str = repr(row)
223
+ self.assertIsInstance(repr_str, str)
224
+ self.assertIn("'name':", repr_str)
225
+
226
+ # Test __str__
227
+ str_repr = str(row)
228
+ self.assertIsInstance(str_repr, str)
229
+ self.assertIn("name", str_repr)
230
+
231
+ def test_row_length(self, tx):
232
+ """Test row length operations."""
233
+ table = tx.table("row_test_basic")
234
+ row = table.select().one()
235
+
236
+ # Test __len__
237
+ length = len(row)
238
+ self.assertEqual(length, 1) # Should be 1 since it represents one row
239
+
240
+ def test_row_item_access(self, tx):
241
+ """Test row item access operations."""
242
+ table = tx.table("row_test_basic")
243
+ row = table.select().one()
244
+
245
+ # Test __getitem__
246
+ name = row["name"]
247
+ self.assertIsInstance(name, str)
248
+
249
+ age = row["age"]
250
+ self.assertIsInstance(age, int)
251
+
252
+ # Test accessing sys_ columns
253
+ sys_id = row["sys_id"]
254
+ self.assertIsNotNone(sys_id)
255
+
256
+ # Test accessing non-existent column
257
+ with self.assertRaises((DbColumnMissingError, KeyError)):
258
+ _ = row["non_existent_column"]
259
+
260
+ def test_row_item_assignment(self, tx):
261
+ """Test row item assignment operations."""
262
+ table = tx.table("row_test_basic")
263
+ row = table.select().one()
264
+ original_name = row["name"]
265
+
266
+ # Test __setitem__
267
+ row["name"] = "Updated Name"
268
+ self.assertEqual(row["name"], "Updated Name")
269
+
270
+ # Test setting new column value
271
+ row["new_field"] = "new_value"
272
+ self.assertEqual(row["new_field"], "new_value")
273
+
274
+ # Test setting None value
275
+ row["metadata"] = None
276
+ self.assertIsNone(row["metadata"])
277
+
278
+ # Test that primary key cannot be updated
279
+ with self.assertRaises(Exception):
280
+ row["sys_id"] = 99999
281
+
282
+ def test_row_item_deletion(self, tx):
283
+ """Test row item deletion operations."""
284
+ table = tx.table("row_test_basic")
285
+ row = table.select().one()
286
+
287
+ # Test __delitem__
288
+ row["metadata"] = "test_data"
289
+ self.assertEqual(row["metadata"], "test_data")
290
+
291
+ del row["metadata"]
292
+ self.assertIsNone(row["metadata"])
293
+
294
+ # Test deleting non-existent field
295
+ del row["non_existent_field"] # Should not raise error
296
+
297
+ # Test that primary key cannot be deleted
298
+ with self.assertRaises(Exception):
299
+ del row["sys_id"]
300
+
301
+ def test_row_contains(self, tx):
302
+ """Test row membership testing."""
303
+ table = tx.table("row_test_basic")
304
+ row = table.select().one()
305
+
306
+ # Test __contains__
307
+ self.assertIn("name", row)
308
+ self.assertIn("age", row)
309
+ self.assertIn("sys_id", row)
310
+ self.assertNotIn("non_existent_column", row)
311
+
312
+ # Test case insensitivity
313
+ self.assertIn("NAME", row) # Should be case insensitive
314
+ self.assertIn("Age", row)
315
+
316
+ def test_row_clear(self, tx):
317
+ """Test row clear operation (delete from database)."""
318
+ table = tx.table("row_test_basic")
319
+
320
+ # Insert a test row to delete
321
+ test_row = table.new({"name": "To Delete", "age": 99, "email": "delete@test.com"})
322
+ test_id = test_row["sys_id"]
323
+
324
+ # Verify it exists
325
+ self.assertIsNotNone(table.find(test_id))
326
+
327
+ # Clear the row
328
+ test_row.clear()
329
+
330
+ # Verify it's deleted
331
+ self.assertIsNone(table.find(test_id))
332
+
333
+ def test_row_keys(self, tx):
334
+ """Test row keys operation."""
335
+ table = tx.table("row_test_basic")
336
+ row = table.select().one()
337
+
338
+ # Test keys()
339
+ keys = row.keys()
340
+ self.assertIsInstance(keys, list)
341
+ self.assertIn("name", keys)
342
+ self.assertIn("age", keys)
343
+ self.assertIn("sys_id", keys)
344
+ self.assertIn("sys_created_by", keys)
345
+ self.assertIn("sys_created_on", keys)
346
+
347
+ def test_row_values(self, tx):
348
+ """Test row values operation."""
349
+ table = tx.table("row_test_basic")
350
+ row = table.select().one()
351
+
352
+ # Test values() without arguments
353
+ values = row.values()
354
+ self.assertIsInstance(values, list)
355
+ self.assertGreater(len(values), 0)
356
+
357
+ # Test values() with specific columns
358
+ specific_values = row.values("name", "age")
359
+ self.assertEqual(len(specific_values), 2)
360
+ self.assertEqual(specific_values[0], row["name"])
361
+ self.assertEqual(specific_values[1], row["age"])
362
+
363
+ def test_row_items(self, tx):
364
+ """Test row items operation."""
365
+ table = tx.table("row_test_basic")
366
+ row = table.select().one()
367
+
368
+ # Test items()
369
+ items = row.items()
370
+ self.assertIsInstance(items, list)
371
+
372
+ # Verify items structure
373
+ for key, value in items:
374
+ self.assertIsInstance(key, str)
375
+ self.assertEqual(value, row[key])
376
+
377
+ def test_row_get_method(self, tx):
378
+ """Test row get method with default values."""
379
+ table = tx.table("row_test_basic")
380
+ row = table.select().one()
381
+
382
+ # Test get() with existing key
383
+ name = row.get("name")
384
+ self.assertEqual(name, row["name"])
385
+
386
+ # Test get() with non-existent key and default
387
+ default_value = row.get("non_existent", "default")
388
+ self.assertEqual(default_value, "default")
389
+
390
+ # Test get() with None value
391
+ row["metadata"] = None
392
+ metadata = row.get("metadata", "fallback")
393
+ self.assertEqual(metadata, "fallback")
394
+
395
+ # Test get() without default
396
+ non_existent = row.get("totally_missing")
397
+ self.assertIsNone(non_existent)
398
+
399
+ def test_row_dictionary_conversion(self, tx):
400
+ """Test converting row to dictionary."""
401
+ table = tx.table("row_test_basic")
402
+ row = table.select().one()
403
+
404
+ # Test to_dict() method if it exists
405
+ if hasattr(row, "to_dict"):
406
+ row_dict = row.to_dict()
407
+ self.assertIsInstance(row_dict, dict)
408
+ self.assertEqual(row_dict["name"], row["name"])
409
+
410
+ # Test dict() conversion
411
+ row_as_dict = dict(row)
412
+ self.assertIsInstance(row_as_dict, dict)
413
+ self.assertEqual(row_as_dict["name"], row["name"])
414
+
415
+ def test_row_update_operations(self, tx):
416
+ """Test various row update operations."""
417
+ table = tx.table("row_test_basic")
418
+ row = table.select().one()
419
+
420
+ # Test single field update
421
+ original_score = row["score"]
422
+ row["score"] = 99.9
423
+ self.assertEqual(row["score"], 99.9)
424
+
425
+ # Test multiple field updates
426
+ row["name"] = "Bulk Updated"
427
+ row["age"] = 999
428
+ row["active"] = not row["active"]
429
+
430
+ self.assertEqual(row["name"], "Bulk Updated")
431
+ self.assertEqual(row["age"], 999)
432
+
433
+ # Test updating with different data types
434
+ row["score"] = 100 # int to float column
435
+ self.assertEqual(row["score"], 100)
436
+
437
+ def test_row_constraint_violations(self, tx):
438
+ """Test row operations with constraint violations."""
439
+ table = tx.table("row_test_constraints")
440
+
441
+ # Get existing row
442
+ row = table.select().one()
443
+
444
+ # Test unique constraint violation
445
+ try:
446
+ # Try to update to existing username
447
+ row["username"] = "user1" # This should fail if user1 exists
448
+ except (DbDuplicateKeyError, Exception):
449
+ pass # Expected to fail
450
+
451
+ # Test that row is still usable after error
452
+ row["phone"] = "555-9999"
453
+ self.assertEqual(row["phone"], "555-9999")
454
+
455
+ def test_row_concurrent_updates(self, tx):
456
+ """Test row updates under concurrent access."""
457
+ table = tx.table("row_test_concurrent")
458
+
459
+ # Get a row for concurrent testing
460
+ test_row = table.select().one()
461
+ row_id = test_row["sys_id"]
462
+
463
+ def worker(worker_id, iterations=5):
464
+ """Worker function for concurrent row updates."""
465
+ results = []
466
+ for i in range(iterations):
467
+ try:
468
+ # Get fresh row instance
469
+ row = table.row(row_id)
470
+
471
+ # Update counter
472
+ current_counter = row["counter"] or 0
473
+ row["counter"] = current_counter + 1
474
+ row["worker_id"] = f"worker_{worker_id}"
475
+ row["last_updated"] = f"2023-01-01T{i:02d}:00:00"
476
+
477
+ results.append(("success", row["counter"]))
478
+ time.sleep(0.001) # Small delay
479
+
480
+ except Exception as e:
481
+ results.append(("error", str(e)))
482
+
483
+ return results
484
+
485
+ # Run concurrent workers
486
+ with ThreadPoolExecutor(max_workers=3) as executor:
487
+ futures = [executor.submit(worker, i, 3) for i in range(3)]
488
+ all_results = []
489
+ for future in as_completed(futures):
490
+ all_results.extend(future.result())
491
+
492
+ # Verify operations completed
493
+ self.assertGreater(len(all_results), 0)
494
+
495
+ # Check final state
496
+ final_row = table.row(row_id)
497
+ self.assertIsNotNone(final_row["counter"])
498
+
499
+ def test_row_relationship_operations(self, tx):
500
+ """Test row operations with foreign key relationships."""
501
+ parent_table = tx.table("row_test_parent")
502
+ child_table = tx.table("row_test_child")
503
+
504
+ # Get parent row
505
+ parent = parent_table.select().one()
506
+ parent_id = parent["sys_id"]
507
+
508
+ # Get related child rows
509
+ children = child_table.select(where={"parent_id": parent_id})
510
+ child_list = list(children)
511
+ self.assertGreater(len(child_list), 0)
512
+
513
+ # Update parent
514
+ parent["priority"] = 999
515
+ self.assertEqual(parent["priority"], 999)
516
+
517
+ # Update child
518
+ child = child_list[0]
519
+ child["value"] = 888.8
520
+ self.assertEqual(child["value"], 888.8)
521
+
522
+ # Test orphaning (if constraints allow)
523
+ try:
524
+ child["parent_id"] = 99999 # Non-existent parent
525
+ except Exception:
526
+ pass # Expected to fail due to foreign key constraint
527
+
528
+ def test_row_edge_cases_null_values(self, tx):
529
+ """Test row operations with NULL values."""
530
+ table = tx.table("row_test_basic")
531
+
532
+ # Insert row with NULL values
533
+ row = table.new({
534
+ "name": "NULL Test",
535
+ "age": None,
536
+ "email": None,
537
+ "active": None,
538
+ "score": None,
539
+ "metadata": None
540
+ })
541
+
542
+ # Test accessing NULL values
543
+ self.assertEqual(row["name"], "NULL Test")
544
+ self.assertIsNone(row["age"])
545
+ self.assertIsNone(row["email"])
546
+ self.assertIsNone(row["active"])
547
+ self.assertIsNone(row["score"])
548
+ self.assertIsNone(row["metadata"])
549
+
550
+ # Test updating NULL values
551
+ row["age"] = 25
552
+ self.assertEqual(row["age"], 25)
553
+
554
+ # Test setting back to NULL
555
+ row["age"] = None
556
+ self.assertIsNone(row["age"])
557
+
558
+ def test_row_edge_cases_unicode_data(self, tx):
559
+ """Test row operations with Unicode data."""
560
+ table = tx.table("row_test_basic")
561
+
562
+ # Insert row with Unicode data
563
+ unicode_data = {
564
+ "name": "José María 🚀",
565
+ "email": "josé@español.com",
566
+ "metadata": "Unicode test: αβγδε 中文 🎉"
567
+ }
568
+
569
+ row = table.new(unicode_data)
570
+
571
+ # Test accessing Unicode data
572
+ self.assertEqual(row["name"], "José María 🚀")
573
+ self.assertEqual(row["email"], "josé@español.com")
574
+ self.assertIn("αβγδε", row["metadata"])
575
+
576
+ # Test updating Unicode data
577
+ row["name"] = "Updated José 🎯"
578
+ self.assertEqual(row["name"], "Updated José 🎯")
579
+
580
+ def test_row_edge_cases_large_data(self, tx):
581
+ """Test row operations with large data."""
582
+ table = tx.table("row_test_basic")
583
+
584
+ # Insert row with large data
585
+ large_string = "x" * 10000 # 10KB string
586
+ row = table.new({
587
+ "name": "Large Data User",
588
+ "metadata": large_string
589
+ })
590
+
591
+ # Test accessing large data
592
+ self.assertEqual(row["name"], "Large Data User")
593
+ self.assertEqual(len(row["metadata"]), 10000)
594
+
595
+ # Test updating large data
596
+ even_larger = "y" * 20000 # 20KB string
597
+ row["metadata"] = even_larger
598
+ self.assertEqual(len(row["metadata"]), 20000)
599
+
600
+ def test_row_edge_cases_special_characters(self, tx):
601
+ """Test row operations with special characters."""
602
+ table = tx.table("row_test_basic")
603
+
604
+ # Special characters that might cause issues
605
+ special_data = {
606
+ "name": "Special'Char\"User",
607
+ "email": "test@example.com",
608
+ "metadata": "Data with 'quotes' and \"double quotes\" and \n newlines \t tabs"
609
+ }
610
+
611
+ row = table.new(special_data)
612
+
613
+ # Test accessing special character data
614
+ self.assertIn("'", row["name"])
615
+ self.assertIn('"', row["name"])
616
+ self.assertIn("'quotes'", row["metadata"])
617
+ self.assertIn('\n', row["metadata"])
618
+ self.assertIn('\t', row["metadata"])
619
+
620
+ def test_row_error_recovery(self, tx):
621
+ """Test row error recovery scenarios."""
622
+ table = tx.table("row_test_basic")
623
+ row = table.select().one()
624
+
625
+ # Test recovery from column access error
626
+ try:
627
+ _ = row["completely_invalid_column"]
628
+ except Exception:
629
+ # Should still be able to access valid columns
630
+ name = row["name"]
631
+ self.assertIsNotNone(name)
632
+
633
+ # Test recovery from update error
634
+ try:
635
+ row["invalid_column"] = "value"
636
+ except Exception:
637
+ # Should still be able to update valid columns
638
+ row["name"] = "Recovered User"
639
+ self.assertEqual(row["name"], "Recovered User")
640
+
641
+ def test_row_lock_operations(self, tx):
642
+ """Test row locking operations if supported."""
643
+ table = tx.table("row_test_basic")
644
+
645
+ # Test row with lock parameter
646
+ first_row_data = table.select().one()
647
+ sys_id = first_row_data["sys_id"]
648
+
649
+ try:
650
+ # Some databases support row locking
651
+ locked_row = table.row(sys_id, lock=True)
652
+ self.assertIsInstance(locked_row, Row)
653
+
654
+ # Test operations on locked row
655
+ locked_row["name"] = "Locked User"
656
+ self.assertEqual(locked_row["name"], "Locked User")
657
+
658
+ except Exception:
659
+ # Row locking might not be supported
660
+ pass
661
+
662
+ def test_row_key_column_operations(self, tx):
663
+ """Test row operations with different key column configurations."""
664
+ table = tx.table("row_test_basic")
665
+
666
+ # Test key_cols property
667
+ row = table.select().one()
668
+ if hasattr(row, "key_cols"):
669
+ key_cols = row.key_cols
670
+ self.assertIsInstance(key_cols, list)
671
+ self.assertIn("sys_id", key_cols)
672
+
673
+ def test_row_caching_behavior(self, tx):
674
+ """Test row caching and data consistency."""
675
+ table = tx.table("row_test_basic")
676
+ row1 = table.select().one()
677
+ row_id = row1["sys_id"]
678
+
679
+ # Get another instance of the same row
680
+ row2 = table.row(row_id)
681
+
682
+ # Update through first instance
683
+ row1["name"] = "Updated via row1"
684
+
685
+ # Check if second instance sees the change
686
+ # (behavior depends on caching implementation)
687
+ name_via_row2 = row2["name"]
688
+ # This test documents current behavior rather than asserting specific behavior
689
+
690
+ def test_row_data_type_coercion(self, tx):
691
+ """Test row data type coercion and validation."""
692
+ table = tx.table("row_test_basic")
693
+ row = table.select().one()
694
+
695
+ # Test type coercion
696
+ row["age"] = "30" # String to int
697
+ self.assertEqual(row["age"], 30)
698
+
699
+ row["score"] = "95.5" # String to float
700
+ self.assertEqual(row["score"], 95.5)
701
+
702
+ row["active"] = "true" # String to bool (if supported)
703
+ # Behavior may vary by implementation
704
+
705
+
706
+ if __name__ == "__main__":
707
+ unittest.main()