familia 2.0.0.pre15 → 2.0.0.pre16

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.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/code-quality.yml +138 -0
  3. data/.github/workflows/code-smellage.yml +145 -0
  4. data/.github/workflows/docs.yml +31 -8
  5. data/.gitignore +1 -1
  6. data/.pre-commit-config.yaml +7 -1
  7. data/.reek.yml +98 -0
  8. data/.rubocop.yml +48 -10
  9. data/.talismanrc +9 -0
  10. data/.yardopts +18 -13
  11. data/CHANGELOG.rst +64 -4
  12. data/CLAUDE.md +1 -1
  13. data/Gemfile +6 -5
  14. data/Gemfile.lock +99 -23
  15. data/LICENSE.txt +1 -1
  16. data/README.md +285 -85
  17. data/changelog.d/README.md +2 -2
  18. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  19. data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
  20. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  21. data/docs/archive/README.md +3 -2
  22. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  23. data/docs/conf.py +29 -0
  24. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  25. data/docs/guides/feature-encrypted-fields.md +785 -0
  26. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  27. data/docs/guides/feature-external-identifiers.md +637 -0
  28. data/docs/guides/feature-object-identifiers.md +435 -0
  29. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  30. data/docs/guides/feature-relationships-methods.md +684 -0
  31. data/docs/guides/feature-relationships.md +200 -0
  32. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  33. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  34. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  35. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  36. data/docs/guides/index.md +176 -0
  37. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  38. data/docs/migrating/v2.0.0-pre.md +1 -1
  39. data/docs/migrating/v2.0.0-pre11.md +2 -2
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre5.md +33 -12
  42. data/docs/migrating/v2.0.0-pre6.md +2 -2
  43. data/docs/migrating/v2.0.0-pre7.md +8 -8
  44. data/docs/overview.md +623 -19
  45. data/docs/reference/api-technical.md +1365 -0
  46. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  47. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  48. data/examples/autoloader/mega_customer.rb +3 -1
  49. data/examples/encrypted_fields.rb +378 -0
  50. data/examples/json_usage_patterns.rb +144 -0
  51. data/examples/relationships.rb +13 -13
  52. data/examples/safe_dump.rb +6 -6
  53. data/examples/single_connection_transaction_confusions.rb +379 -0
  54. data/lib/familia/base.rb +49 -10
  55. data/lib/familia/connection/handlers.rb +223 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  57. data/lib/familia/connection/middleware.rb +75 -0
  58. data/lib/familia/connection/operation_core.rb +93 -0
  59. data/lib/familia/connection/operations.rb +277 -0
  60. data/lib/familia/connection/pipeline_core.rb +87 -0
  61. data/lib/familia/connection/transaction_core.rb +100 -0
  62. data/lib/familia/connection.rb +60 -186
  63. data/lib/familia/data_type/commands.rb +53 -51
  64. data/lib/familia/data_type/serialization.rb +108 -107
  65. data/lib/familia/data_type/types/counter.rb +1 -1
  66. data/lib/familia/data_type/types/hashkey.rb +13 -10
  67. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  68. data/lib/familia/data_type/types/lock.rb +3 -2
  69. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  70. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  71. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  72. data/lib/familia/data_type.rb +75 -47
  73. data/lib/familia/distinguisher.rb +85 -0
  74. data/lib/familia/encryption/encrypted_data.rb +15 -24
  75. data/lib/familia/encryption/manager.rb +6 -4
  76. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  77. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  78. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  79. data/lib/familia/encryption/request_cache.rb +7 -7
  80. data/lib/familia/encryption.rb +2 -3
  81. data/lib/familia/errors.rb +9 -3
  82. data/lib/familia/features/autoloader.rb +30 -12
  83. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  84. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  85. data/lib/familia/features/encrypted_fields.rb +66 -64
  86. data/lib/familia/features/expiration/extensions.rb +1 -1
  87. data/lib/familia/features/expiration.rb +31 -26
  88. data/lib/familia/features/external_identifier.rb +9 -12
  89. data/lib/familia/features/object_identifier.rb +56 -19
  90. data/lib/familia/features/quantization.rb +16 -21
  91. data/lib/familia/features/relationships/README.md +97 -0
  92. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  93. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  95. data/lib/familia/features/relationships/indexing.rb +176 -256
  96. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  98. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  99. data/lib/familia/features/relationships/participation.rb +656 -0
  100. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  101. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  102. data/lib/familia/features/relationships.rb +65 -266
  103. data/lib/familia/features/safe_dump.rb +127 -130
  104. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  105. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  106. data/lib/familia/features/transient_fields.rb +3 -5
  107. data/lib/familia/features.rb +4 -13
  108. data/lib/familia/field_type.rb +24 -4
  109. data/lib/familia/horreum/core/connection.rb +229 -26
  110. data/lib/familia/horreum/core/database_commands.rb +27 -17
  111. data/lib/familia/horreum/core/serialization.rb +40 -20
  112. data/lib/familia/horreum/core/utils.rb +2 -1
  113. data/lib/familia/horreum/shared/settings.rb +2 -1
  114. data/lib/familia/horreum/subclass/definition.rb +33 -45
  115. data/lib/familia/horreum/subclass/management.rb +72 -24
  116. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  117. data/lib/familia/horreum.rb +196 -114
  118. data/lib/familia/json_serializer.rb +0 -1
  119. data/lib/familia/logging.rb +11 -114
  120. data/lib/familia/refinements/dear_json.rb +122 -0
  121. data/lib/familia/refinements/logger_trace.rb +20 -17
  122. data/lib/familia/refinements/stylize_words.rb +65 -0
  123. data/lib/familia/refinements/time_literals.rb +60 -52
  124. data/lib/familia/refinements.rb +2 -1
  125. data/lib/familia/secure_identifier.rb +60 -28
  126. data/lib/familia/settings.rb +83 -7
  127. data/lib/familia/utils.rb +5 -87
  128. data/lib/familia/verifiable_identifier.rb +4 -4
  129. data/lib/familia/version.rb +1 -1
  130. data/lib/familia.rb +72 -14
  131. data/lib/middleware/database_middleware.rb +56 -14
  132. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  133. data/try/configuration/scenarios_try.rb +1 -1
  134. data/try/connection/fiber_context_preservation_try.rb +250 -0
  135. data/try/connection/handler_constraints_try.rb +59 -0
  136. data/try/connection/operation_mode_guards_try.rb +208 -0
  137. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  138. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  139. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  140. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  141. data/try/connection/transaction_mode_strict_try.rb +98 -0
  142. data/try/connection/transaction_mode_warn_try.rb +131 -0
  143. data/try/connection/transaction_modes_try.rb +249 -0
  144. data/try/core/autoloader_try.rb +120 -2
  145. data/try/core/connection_try.rb +7 -7
  146. data/try/core/conventional_inheritance_try.rb +130 -0
  147. data/try/core/create_method_try.rb +15 -23
  148. data/try/core/database_consistency_try.rb +10 -10
  149. data/try/core/errors_try.rb +8 -11
  150. data/try/core/familia_extended_try.rb +2 -2
  151. data/try/core/familia_members_methods_try.rb +76 -0
  152. data/try/core/isolated_dbclient_try.rb +165 -0
  153. data/try/core/middleware_try.rb +16 -16
  154. data/try/core/persistence_operations_try.rb +4 -4
  155. data/try/core/pools_try.rb +42 -26
  156. data/try/core/secure_identifier_try.rb +28 -24
  157. data/try/core/time_utils_try.rb +10 -10
  158. data/try/core/tools_try.rb +1 -1
  159. data/try/core/utils_try.rb +2 -2
  160. data/try/data_types/boolean_try.rb +4 -4
  161. data/try/data_types/datatype_base_try.rb +0 -2
  162. data/try/data_types/list_try.rb +10 -10
  163. data/try/data_types/sorted_set_try.rb +5 -5
  164. data/try/data_types/string_try.rb +12 -12
  165. data/try/data_types/unsortedset_try.rb +33 -0
  166. data/try/debugging/cache_behavior_tracer.rb +7 -7
  167. data/try/debugging/debug_aad_process.rb +1 -1
  168. data/try/debugging/debug_concealed_internal.rb +1 -1
  169. data/try/debugging/debug_cross_context.rb +1 -1
  170. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  171. data/try/debugging/encryption_method_tracer.rb +10 -10
  172. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  173. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  174. data/try/encryption/config_persistence_try.rb +2 -2
  175. data/try/encryption/encryption_core_try.rb +19 -19
  176. data/try/encryption/instance_variable_scope_try.rb +1 -1
  177. data/try/encryption/module_loading_try.rb +2 -2
  178. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  179. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  180. data/try/encryption/secure_memory_handling_try.rb +1 -1
  181. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  182. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  183. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  184. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  185. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  186. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  187. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  188. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  189. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  190. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  191. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  192. data/try/features/feature_dependencies_try.rb +3 -3
  193. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  194. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  195. data/try/features/quantization/quantization_try.rb +1 -1
  196. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  197. data/try/features/relationships/indexing_try.rb +433 -0
  198. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  199. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  200. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  201. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  202. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  203. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  204. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  205. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  206. data/try/features/relationships/relationships_performance_try.rb +20 -20
  207. data/try/features/relationships/relationships_try.rb +27 -38
  208. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  209. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  210. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  211. data/try/helpers/test_cleanup.rb +86 -0
  212. data/try/helpers/test_helpers.rb +3 -3
  213. data/try/horreum/base_try.rb +3 -2
  214. data/try/horreum/commands_try.rb +1 -1
  215. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  216. data/try/horreum/initialization_try.rb +11 -7
  217. data/try/horreum/relations_try.rb +21 -13
  218. data/try/horreum/serialization_try.rb +12 -11
  219. data/try/integration/cross_component_try.rb +3 -3
  220. data/try/memory/memory_basic_test.rb +1 -1
  221. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  222. data/try/models/customer_safe_dump_try.rb +1 -1
  223. data/try/models/customer_try.rb +8 -10
  224. data/try/models/datatype_base_try.rb +3 -3
  225. data/try/models/familia_object_try.rb +9 -8
  226. data/try/performance/benchmarks_try.rb +2 -2
  227. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  228. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  229. data/try/prototypes/atomic_saves_v4.rb +1 -1
  230. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  231. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  232. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  234. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  235. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  236. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  237. data/try/prototypes/pooling/pool_siege.rb +11 -11
  238. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  239. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  240. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  241. data/try/refinements/logger_trace_methods_try.rb +44 -0
  242. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  243. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  244. metadata +75 -43
  245. data/.rubocop_todo.yml +0 -208
  246. data/docs/connection_pooling.md +0 -192
  247. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  248. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  249. data/docs/guides/Feature-System-Autoloading.md +0 -198
  250. data/docs/guides/Home.md +0 -116
  251. data/docs/guides/Relationships-Guide.md +0 -737
  252. data/docs/guides/relationships-methods.md +0 -266
  253. data/docs/reference/auditing_database_commands.rb +0 -228
  254. data/examples/permissions.rb +0 -240
  255. data/lib/familia/features/relationships/cascading.rb +0 -437
  256. data/lib/familia/features/relationships/membership.rb +0 -497
  257. data/lib/familia/features/relationships/permission_management.rb +0 -264
  258. data/lib/familia/features/relationships/querying.rb +0 -615
  259. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  260. data/lib/familia/features/relationships/tracking.rb +0 -418
  261. data/lib/familia/refinements/snake_case.rb +0 -40
  262. data/lib/familia/validation/command_recorder.rb +0 -336
  263. data/lib/familia/validation/expectations.rb +0 -519
  264. data/lib/familia/validation/validation_helpers.rb +0 -443
  265. data/lib/familia/validation/validator.rb +0 -412
  266. data/lib/familia/validation.rb +0 -140
  267. data/try/data_types/set_try.rb +0 -33
  268. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  269. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  270. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  271. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  272. data/try/validation/command_validation_try.rb.disabled +0 -207
  273. data/try/validation/performance_validation_try.rb.disabled +0 -324
  274. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -26,7 +26,7 @@ plaintext = "sensitive data here"
26
26
  Familia.config.encryption_keys = test_keys
27
27
  Familia.config.current_key_version = :v2
28
28
  encrypted = Familia::Encryption.encrypt(plaintext, context: context)
29
- encrypted_data = JSON.parse(encrypted, symbolize_names: true)
29
+ encrypted_data = Familia::JsonSerializer.parse(encrypted, symbolize_names: true)
30
30
  encrypted_data[:algorithm]
31
31
  #=> "xchacha20poly1305"
32
32
 
@@ -38,7 +38,7 @@ plaintext = "sensitive data here"
38
38
  Familia.config.encryption_keys = test_keys
39
39
  Familia.config.current_key_version = :v2
40
40
  encrypted = Familia::Encryption.encrypt(plaintext, context: context)
41
- encrypted_data = JSON.parse(encrypted, symbolize_names: true)
41
+ encrypted_data = Familia::JsonSerializer.parse(encrypted, symbolize_names: true)
42
42
  encrypted_data[:key_version]
43
43
  #=> "v2"
44
44
 
@@ -109,13 +109,13 @@ Familia.config.encryption_keys = test_keys
109
109
  Familia.config.current_key_version = :v1
110
110
 
111
111
  # Create encrypted data with unknown algorithm. Error should not leak algorithm details.
112
- invalid_encrypted = {
112
+ invalid_encrypted = Familia::JsonSerializer.dump({
113
113
  algorithm: "unknown-cipher",
114
114
  key_version: "v1",
115
115
  nonce: Base64.strict_encode64("x" * 12),
116
116
  ciphertext: Base64.strict_encode64("encrypted_data"),
117
117
  auth_tag: Base64.strict_encode64("y" * 16)
118
- }.to_json
118
+ })
119
119
 
120
120
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
121
121
  #=!> Familia::EncryptionError
@@ -141,13 +141,13 @@ Familia.config.encryption_keys = test_keys
141
141
  Familia.config.current_key_version = :v1
142
142
 
143
143
  # Create encrypted data with invalid base64 nonce
144
- invalid_encrypted = {
144
+ invalid_encrypted = Familia::JsonSerializer.dump({
145
145
  algorithm: "aes-256-gcm",
146
146
  key_version: "v1",
147
147
  nonce: "invalid_base64!@#",
148
148
  ciphertext: Base64.strict_encode64("encrypted_data"),
149
149
  auth_tag: Base64.strict_encode64("y" * 16)
150
- }.to_json
150
+ })
151
151
 
152
152
  begin
153
153
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
@@ -165,13 +165,13 @@ Familia.config.encryption_keys = test_keys
165
165
  Familia.config.current_key_version = :v1
166
166
 
167
167
  # Create encrypted data with invalid base64 auth_tag
168
- invalid_encrypted = {
168
+ invalid_encrypted = Familia::JsonSerializer.dump({
169
169
  algorithm: "aes-256-gcm",
170
170
  key_version: "v1",
171
171
  nonce: Base64.strict_encode64("x" * 12),
172
172
  ciphertext: Base64.strict_encode64("encrypted_data"),
173
173
  auth_tag: "invalid_base64!@#"
174
- }.to_json
174
+ })
175
175
 
176
176
  begin
177
177
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
@@ -189,13 +189,13 @@ Familia.config.encryption_keys = test_keys
189
189
  Familia.config.current_key_version = :v1
190
190
 
191
191
  # Create encrypted data with wrong nonce size (8 bytes instead of 12)
192
- invalid_encrypted = {
192
+ invalid_encrypted = Familia::JsonSerializer.dump({
193
193
  algorithm: "aes-256-gcm",
194
194
  key_version: "v1",
195
195
  nonce: Base64.strict_encode64("x" * 8),
196
196
  ciphertext: Base64.strict_encode64("encrypted_data"),
197
197
  auth_tag: Base64.strict_encode64("y" * 16)
198
- }.to_json
198
+ })
199
199
 
200
200
  begin
201
201
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
@@ -213,13 +213,13 @@ Familia.config.encryption_keys = test_keys
213
213
  Familia.config.current_key_version = :v1
214
214
 
215
215
  # Create encrypted data with wrong auth_tag size (8 bytes instead of 16)
216
- invalid_encrypted = {
216
+ invalid_encrypted = Familia::JsonSerializer.dump({
217
217
  algorithm: "aes-256-gcm",
218
218
  key_version: "v1",
219
219
  nonce: Base64.strict_encode64("x" * 12),
220
220
  ciphertext: Base64.strict_encode64("encrypted_data"),
221
221
  auth_tag: Base64.strict_encode64("y" * 8)
222
- }.to_json
222
+ })
223
223
 
224
224
  begin
225
225
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
@@ -237,12 +237,12 @@ Familia.config.encryption_keys = test_keys
237
237
  Familia.config.current_key_version = :v1
238
238
 
239
239
  # Create encrypted data missing nonce field
240
- invalid_encrypted = {
240
+ invalid_encrypted = Familia::JsonSerializer.dump({
241
241
  algorithm: "aes-256-gcm",
242
242
  key_version: "v1",
243
243
  ciphertext: Base64.strict_encode64("encrypted_data"),
244
244
  auth_tag: Base64.strict_encode64("y" * 16)
245
- }.to_json
245
+ })
246
246
 
247
247
  begin
248
248
  Familia::Encryption.decrypt(invalid_encrypted, context: context)
@@ -284,7 +284,7 @@ plaintext = "algorithm check"
284
284
  Familia.config.encryption_keys = test_keys
285
285
  Familia.config.current_key_version = :v1
286
286
  encrypted_xchacha = Familia::Encryption.encrypt_with('xchacha20poly1305', plaintext, context: context)
287
- encrypted_data_xchacha = JSON.parse(encrypted_xchacha, symbolize_names: true)
287
+ encrypted_data_xchacha = Familia::JsonSerializer.parse(encrypted_xchacha, symbolize_names: true)
288
288
  encrypted_data_xchacha[:algorithm]
289
289
  #=> "xchacha20poly1305"
290
290
 
@@ -296,7 +296,7 @@ plaintext = "algorithm check"
296
296
  Familia.config.encryption_keys = test_keys
297
297
  Familia.config.current_key_version = :v1
298
298
  encrypted_aes = Familia::Encryption.encrypt_with('aes-256-gcm', plaintext, context: context)
299
- encrypted_data_aes = JSON.parse(encrypted_aes, symbolize_names: true)
299
+ encrypted_data_aes = Familia::JsonSerializer.parse(encrypted_aes, symbolize_names: true)
300
300
  encrypted_data_aes[:algorithm]
301
301
  #=> "aes-256-gcm"
302
302
 
@@ -308,7 +308,7 @@ plaintext = "nonce size test"
308
308
  Familia.config.encryption_keys = test_keys
309
309
  Familia.config.current_key_version = :v1
310
310
  encrypted_xchacha = Familia::Encryption.encrypt_with('xchacha20poly1305', plaintext, context: context)
311
- encrypted_data_xchacha = JSON.parse(encrypted_xchacha, symbolize_names: true)
311
+ encrypted_data_xchacha = Familia::JsonSerializer.parse(encrypted_xchacha, symbolize_names: true)
312
312
  nonce_bytes_xchacha = Base64.strict_decode64(encrypted_data_xchacha[:nonce])
313
313
  nonce_bytes_xchacha.length
314
314
  #=> 24
@@ -321,10 +321,10 @@ plaintext = "nonce size test"
321
321
  Familia.config.encryption_keys = test_keys
322
322
  Familia.config.current_key_version = :v1
323
323
  encrypted_aes = Familia::Encryption.encrypt_with('aes-256-gcm', plaintext, context: context)
324
- encrypted_data_aes = JSON.parse(encrypted_aes, symbolize_names: true)
324
+ encrypted_data_aes = Familia::JsonSerializer.parse(encrypted_aes, symbolize_names: true)
325
325
  nonce_bytes_aes = Base64.strict_decode64(encrypted_data_aes[:nonce])
326
326
  nonce_bytes_aes.length
327
327
  #=> 12
328
328
 
329
329
  # TEARDOWN
330
- Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
330
+ Fiber[:familia_key_cache]&.clear if Fiber[:familia_key_cache]
@@ -20,7 +20,7 @@ require_relative '../helpers/test_helpers'
20
20
  @test_keys[:v1].nil?
21
21
  #=> false
22
22
 
23
- ## Set config and check immediately in same test
23
+ ## UnsortedSet config and check immediately in same test
24
24
  Familia.config.encryption_keys = @test_keys
25
25
  Familia.config.current_key_version = :v1
26
26
  result = Familia::Encryption.encrypt('test', context: 'test')
@@ -13,12 +13,12 @@ require_relative '../helpers/test_helpers'
13
13
  defined?(Familia::Encryption)
14
14
  #=> "constant"
15
15
 
16
- ## Set and check configuration directly in test
16
+ ## UnsortedSet and check configuration directly in test
17
17
  Familia.encryption_keys = { v1: Base64.strict_encode64('a' * 32) }
18
18
  Familia.encryption_keys.is_a?(Hash)
19
19
  #=> true
20
20
 
21
- ## Set and check current key version directly in test
21
+ ## UnsortedSet and check current key version directly in test
22
22
  Familia.current_key_version = :v1
23
23
  Familia.current_key_version
24
24
  #=> :v1
@@ -175,4 +175,4 @@ derived_key1 == derived_key2
175
175
  #=> true
176
176
 
177
177
  # TEARDOWN
178
- Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
178
+ Fiber[:familia_key_cache]&.clear if Fiber[:familia_key_cache]
@@ -166,4 +166,4 @@ test_key.length
166
166
  #=> 0
167
167
 
168
168
  # TEARDOWN
169
- Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
169
+ Fiber[:familia_key_cache]&.clear if Fiber[:familia_key_cache]
@@ -122,4 +122,4 @@ nonce.bytesize
122
122
  #=> "xchacha20poly1305-secure"
123
123
 
124
124
  # TEARDOWN
125
- Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
125
+ Fiber[:familia_key_cache]&.clear if Fiber[:familia_key_cache]
@@ -77,15 +77,19 @@ end
77
77
  @doc.content.to_str
78
78
  #=!> NoMethodError
79
79
 
80
- ## JSON serialization - to_json (fails for security)
80
+ ## JSON serialization - to_json (fails for security) - Basic tryouts testcase
81
81
  begin
82
- @doc.content.to_json
83
- raise "Should have raised SerializerError"
82
+ Familia::JsonSerializer.dump(@doc.content)
84
83
  rescue Familia::SerializerError => e
85
84
  e.class
86
85
  end
87
86
  #=> Familia::SerializerError
88
87
 
88
+ ## JSON serialization - to_json (fails for security) - Robust tryout testcase
89
+ Familia::JsonSerializer.dump(@doc.content)
90
+ #=:> Familia::SerializerError
91
+ #=~> /Failed to dump ConcealedString/
92
+
89
93
  ## JSON serialization - as_json
90
94
  @doc.content.as_json
91
95
  #=> "[CONCEALED]"
@@ -147,7 +151,7 @@ end
147
151
 
148
152
  ## Encrypted data is valid JSON
149
153
  begin
150
- parsed = JSON.parse(@encrypted_data)
154
+ parsed = Familia::JsonSerializer.parse(@encrypted_data)
151
155
  parsed.key?('algorithm')
152
156
  rescue
153
157
  false
@@ -218,15 +222,15 @@ debug_array.map(&:to_s)
218
222
  ## Debug what's actually in the database
219
223
  @all_keys = Familia.dbclient.keys("*")
220
224
  @all_keys
221
- #=> ["secretdocument:test123:object"]
225
+ #=> ["secret_document:test123:object", "secret_document:instances"]
222
226
 
223
227
  ## Check database storage - should be encrypted
224
- @db_hash = Familia.dbclient.hgetall("secretdocument:test123:object")
228
+ @db_hash = Familia.dbclient.hgetall("secret_document:test123:object")
225
229
  @db_hash.keys
226
230
  #=> ["id", "title", "content", "api_key"]
227
231
 
228
232
  ## Database storage contains encrypted string
229
- db_content = Familia.dbclient.hget("secretdocument:test123:object", "content")
233
+ db_content = Familia.dbclient.hget("secret_document:test123:object", "content")
230
234
  db_content&.class&.name || "nil"
231
235
  #=> "String"
232
236
 
@@ -122,4 +122,4 @@ end
122
122
  @user2.api_key.reveal { |decrypted| decrypted }
123
123
  #=> 'secret-key-123'
124
124
 
125
- Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
125
+ Fiber[:familia_key_cache]&.clear if Fiber[:familia_key_cache]
@@ -138,7 +138,7 @@ end
138
138
  @model4 = FullSecureModel4.new(model_id: 'secure-126')
139
139
  @model4.password = 'secure-pass'
140
140
  @model4.activity_log << 'User logged in'
141
- @model4.metadata['last_login'] = Time.now.to_i.to_s
141
+ @model4.metadata['last_login'] = Familia.now.to_i.to_s
142
142
 
143
143
  [@model4.password.to_s, @model4.activity_log.size, @model4.metadata.has_key?('last_login')]
144
144
  #=> ['[CONCEALED]', 1, true]
@@ -189,7 +189,7 @@ end
189
189
  # Test shows that algorithm parameter is currently ignored - XChaCha20Poly1305 is used by default
190
190
  concealed_data = @aes_model.secret_data
191
191
  encrypted_json = concealed_data.encrypted_value
192
- parsed_data = JSON.parse(encrypted_json, symbolize_names: true)
192
+ parsed_data = Familia::JsonSerializer.parse(encrypted_json, symbolize_names: true)
193
193
  [parsed_data[:algorithm], @aes_model.secret_data.reveal { |data| data }]
194
194
  #=> ["xchacha20poly1305", "aes-gcm integration test"]
195
195
 
@@ -211,6 +211,6 @@ end
211
211
  # Verify algorithm and decryption
212
212
  concealed_data = @aes_model2.secret_data
213
213
  encrypted_json = concealed_data.encrypted_value
214
- parsed_data = JSON.parse(encrypted_json, symbolize_names: true)
214
+ parsed_data = Familia::JsonSerializer.parse(encrypted_json, symbolize_names: true)
215
215
  [parsed_data[:algorithm], @aes_model2.secret_data.reveal { |data| data }]
216
216
  #=> ["xchacha20poly1305", "aes-gcm integration test"]
@@ -14,7 +14,7 @@ Familia.config.current_key_version = :v1
14
14
 
15
15
  ## No persistent key cache exists
16
16
  ## Verify that we don't maintain a key cache at all
17
- Thread.current[:familia_key_cache]
17
+ Fiber[:familia_key_cache]
18
18
  #=> nil
19
19
 
20
20
  ## Each encryption gets fresh key derivation
@@ -30,7 +30,7 @@ end
30
30
  #=> 'secret-value'
31
31
 
32
32
  ## No cache should be created
33
- Thread.current[:familia_key_cache]
33
+ Fiber[:familia_key_cache]
34
34
  #=> nil
35
35
 
36
36
  ## Reading the value also doesn't create cache
@@ -41,7 +41,7 @@ end
41
41
  #=> 'secret-value'
42
42
 
43
43
  ## repaired test
44
- Thread.current[:familia_key_cache]
44
+ Fiber[:familia_key_cache]
45
45
  #=> nil
46
46
 
47
47
  ## Multiple fields don't share state
@@ -61,7 +61,7 @@ end
61
61
  #=> 'value-c'
62
62
 
63
63
  ## Still no cache after multiple operations
64
- Thread.current[:familia_key_cache]
64
+ Fiber[:familia_key_cache]
65
65
  #=> nil
66
66
 
67
67
  ## All values can be retrieved correctly
@@ -83,7 +83,7 @@ end
83
83
  #=> 'value-c'
84
84
 
85
85
  ## Still no cache
86
- Thread.current[:familia_key_cache]
86
+ Fiber[:familia_key_cache]
87
87
  #=> nil
88
88
 
89
89
  ## Master keys are wiped after each operation
@@ -111,7 +111,7 @@ end.all?
111
111
  #=> true
112
112
 
113
113
  ## Still no cache after multiple operations
114
- Thread.current[:familia_key_cache]
114
+ Fiber[:familia_key_cache]
115
115
  #=> nil
116
116
 
117
117
  ## Thread isolation (no shared state between threads)
@@ -132,7 +132,7 @@ end
132
132
  model.thread_secret = "thread-secret-#{i}"
133
133
 
134
134
  # Verify no cache in this thread
135
- cache_state = Thread.current[:familia_key_cache]
135
+ cache_state = Fiber[:familia_key_cache]
136
136
 
137
137
  # Store results
138
138
  @results << {
@@ -182,7 +182,7 @@ end
182
182
  #=> true
183
183
 
184
184
  ## Still no cache after 100 operations
185
- Thread.current[:familia_key_cache]
185
+ Fiber[:familia_key_cache]
186
186
  #=> nil
187
187
 
188
188
  ## Key rotation works without cache complications
@@ -200,7 +200,7 @@ end
200
200
  @model6.rotated_field = 'encrypted-with-v2'
201
201
 
202
202
  # Still no cache with new key version
203
- Thread.current[:familia_key_cache]
203
+ Fiber[:familia_key_cache]
204
204
  #=> nil
205
205
 
206
206
  ## Value is correctly encrypted/decrypted with v2
@@ -214,6 +214,6 @@ Familia.config.current_key_version = :v1
214
214
  #=> :v1
215
215
 
216
216
  # Teardown
217
- Thread.current[:familia_key_cache] = nil
217
+ Fiber[:familia_key_cache] = nil
218
218
  Familia.config.encryption_keys = nil
219
219
  Familia.config.current_key_version = nil
@@ -140,12 +140,12 @@ user.password = 'test-password'
140
140
  encrypted = user.instance_variable_get(:@password)
141
141
 
142
142
  # Tamper with auth tag
143
- parsed = JSON.parse(encrypted.encrypted_value, symbolize_names: true)
143
+ parsed = Familia::JsonSerializer.parse(encrypted.encrypted_value, symbolize_names: true)
144
144
  original_auth_tag = parsed[:auth_tag]
145
145
  tampered_auth_tag = original_auth_tag.dup
146
146
  tampered_auth_tag[0] = tampered_auth_tag[0] == 'A' ? 'B' : 'A'
147
147
  parsed[:auth_tag] = tampered_auth_tag
148
- tampered_json = parsed.to_json
148
+ tampered_json = Familia::JsonSerializer.dump(parsed)
149
149
 
150
150
  user.instance_variable_set(:@password, tampered_json)
151
151
  begin
@@ -166,12 +166,12 @@ user.password = 'test-password'
166
166
  encrypted = user.instance_variable_get(:@password)
167
167
 
168
168
  # Tamper with ciphertext
169
- parsed = JSON.parse(encrypted.encrypted_value, symbolize_names: true)
169
+ parsed = Familia::JsonSerializer.parse(encrypted.encrypted_value, symbolize_names: true)
170
170
  original_ciphertext = parsed[:ciphertext]
171
171
  tampered_ciphertext = original_ciphertext.dup
172
172
  tampered_ciphertext[0] = tampered_ciphertext[0] == 'A' ? 'B' : 'A'
173
173
  parsed[:ciphertext] = tampered_ciphertext
174
- tampered_json = parsed.to_json
174
+ tampered_json = Familia::JsonSerializer.dump(parsed)
175
175
 
176
176
  user.instance_variable_set(:@password, tampered_json)
177
177
  begin
@@ -192,12 +192,12 @@ user.password = 'test-password'
192
192
  encrypted = user.instance_variable_get(:@password)
193
193
 
194
194
  # Tamper with nonce
195
- parsed = JSON.parse(encrypted.encrypted_value, symbolize_names: true)
195
+ parsed = Familia::JsonSerializer.parse(encrypted.encrypted_value, symbolize_names: true)
196
196
  original_nonce = parsed[:nonce]
197
197
  tampered_nonce = original_nonce.dup
198
198
  tampered_nonce[0] = tampered_nonce[0] == 'A' ? 'B' : 'A'
199
199
  parsed[:nonce] = tampered_nonce
200
- tampered_json = parsed.to_json
200
+ tampered_json = Familia::JsonSerializer.dump(parsed)
201
201
 
202
202
  user.instance_variable_set(:@password, tampered_json)
203
203
  begin
@@ -219,9 +219,9 @@ encrypted_with_v1 = user.instance_variable_get(:@password)
219
219
 
220
220
 
221
221
  # Parse and change key version to non-existent version
222
- parsed = JSON.parse(encrypted_with_v1.encrypted_value, symbolize_names: true)
222
+ parsed = Familia::JsonSerializer.parse(encrypted_with_v1.encrypted_value, symbolize_names: true)
223
223
  parsed[:key_version] = 'v999'
224
- modified_json = parsed.to_json
224
+ modified_json = Familia::JsonSerializer.dump(parsed)
225
225
 
226
226
  user.instance_variable_set(:@password, modified_json)
227
227
  begin
@@ -239,12 +239,12 @@ user.password = 'nonce-test-xchacha'
239
239
  encrypted_with_nonce = user.instance_variable_get(:@password)
240
240
 
241
241
  # Parse and modify nonce (XChaCha20Poly1305 uses 24-byte nonces)
242
- parsed = JSON.parse(encrypted_with_nonce.encrypted_value, symbolize_names: true)
242
+ parsed = Familia::JsonSerializer.parse(encrypted_with_nonce.encrypted_value, symbolize_names: true)
243
243
  original_nonce = parsed[:nonce]
244
244
  # Create a different valid base64 nonce for XChaCha20Poly1305 (24 bytes)
245
245
  different_nonce = Base64.strict_encode64('x' * 24)
246
246
  parsed[:nonce] = different_nonce
247
- modified_json = parsed.to_json
247
+ modified_json = Familia::JsonSerializer.dump(parsed)
248
248
 
249
249
  user.instance_variable_set(:@password, modified_json)
250
250
  begin
@@ -264,12 +264,12 @@ encrypted_aes = Familia::Encryption.encrypt_with('aes-256-gcm', 'nonce-test-aes'
264
264
  user_aes.instance_variable_set(:@password, encrypted_aes)
265
265
 
266
266
  # Parse and modify nonce (AES-GCM uses 12-byte nonces)
267
- parsed_aes = JSON.parse(encrypted_aes, symbolize_names: true)
267
+ parsed_aes = Familia::JsonSerializer.parse(encrypted_aes, symbolize_names: true)
268
268
  original_nonce_aes = parsed_aes[:nonce]
269
269
  # Create a different valid base64 nonce for AES-GCM (12 bytes)
270
270
  different_nonce_aes = Base64.strict_encode64('y' * 12)
271
271
  parsed_aes[:nonce] = different_nonce_aes
272
- modified_json_aes = parsed_aes.to_json
272
+ modified_json_aes = Familia::JsonSerializer.dump(parsed_aes)
273
273
 
274
274
  user_aes.instance_variable_set(:@password, modified_json_aes)
275
275
  begin
@@ -367,9 +367,9 @@ user.password = 'algorithm-test'
367
367
  encrypted = user.instance_variable_get(:@password)
368
368
 
369
369
  # Tamper with algorithm field
370
- parsed = JSON.parse(encrypted.encrypted_value, symbolize_names: true)
370
+ parsed = Familia::JsonSerializer.parse(encrypted.encrypted_value, symbolize_names: true)
371
371
  parsed[:algorithm] = 'unsupported_algorithm'
372
- tampered_json = parsed.to_json
372
+ tampered_json = Familia::JsonSerializer.dump(parsed)
373
373
 
374
374
  user.instance_variable_set(:@password, tampered_json)
375
375
  user.password
@@ -29,9 +29,9 @@ end
29
29
  ## Tampered auth tag fails decryption
30
30
  @model.secret = 'valid-secret'
31
31
  @valid_cipher = @model.secret.encrypted_value
32
- @tampered = JSON.parse(@valid_cipher)
32
+ @tampered = Familia::JsonSerializer.parse(@valid_cipher)
33
33
  @tampered['auth_tag'] = Base64.strict_encode64('tampered' * 4)
34
- @model.instance_variable_set(:@secret, @tampered.to_json)
34
+ @model.instance_variable_set(:@secret, Familia::JsonSerializer.dump(@tampered))
35
35
 
36
36
  @model.secret
37
37
  #=!> Familia::EncryptionError
@@ -58,7 +58,7 @@ Familia::Encryption.reset_derivation_count!
58
58
  # Ensure keys are available for this test
59
59
  Familia.config.encryption_keys = @test_keys
60
60
  @error_model = ErrorTest.new(id: 'err-counter')
61
- # Set valid JSON but with invalid base64 data to trigger decrypt failure after parsing
61
+ # UnsortedSet valid JSON but with invalid base64 data to trigger decrypt failure after parsing
62
62
  @error_model.instance_variable_set(:@secret, '{"algorithm":"aes-256-gcm","nonce":"dGVzdA==","ciphertext":"invalid-base64!!!","auth_tag":"dGVzdA==","key_version":"v1"}')
63
63
  begin
64
64
  @error_model.secret
@@ -72,9 +72,9 @@ Familia::Encryption.derivation_count.value
72
72
  # Ensure keys are available for this test
73
73
  Familia.config.encryption_keys = @test_keys
74
74
  @model.secret = 'test-data'
75
- @cipher_data = JSON.parse(@model.secret.encrypted_value)
75
+ @cipher_data = Familia::JsonSerializer.parse(@model.secret.encrypted_value)
76
76
  @cipher_data['algorithm'] = 'unsupported-algorithm'
77
- @model.instance_variable_set(:@secret, @cipher_data.to_json)
77
+ @model.instance_variable_set(:@secret, Familia::JsonSerializer.dump(@cipher_data))
78
78
 
79
79
  @model.secret
80
80
  #=!> Familia::EncryptionError
@@ -93,9 +93,9 @@ Familia.config.current_key_version = @original_version
93
93
  Familia.config.encryption_keys = @test_keys
94
94
  Familia.config.current_key_version = :v1
95
95
  @model.secret = 'test-data'
96
- @cipher_with_bad_version = JSON.parse(@model.secret.encrypted_value)
96
+ @cipher_with_bad_version = Familia::JsonSerializer.parse(@model.secret.encrypted_value)
97
97
  @cipher_with_bad_version['key_version'] = 'nonexistent'
98
- @model.instance_variable_set(:@secret, @cipher_with_bad_version.to_json)
98
+ @model.instance_variable_set(:@secret, Familia::JsonSerializer.dump(@cipher_with_bad_version))
99
99
  @model.secret
100
100
  #=!> Familia::EncryptionError
101
101
  #==> error.message.include?('No key for version: nonexistent')
@@ -162,7 +162,7 @@ internal_empty.nil?
162
162
  ## Consistent behavior across Ruby restart simulation
163
163
  model = PersistenceTestModel.new(user_id: 'persistence-test')
164
164
  model.persistent_data = 'data-to-persist'
165
- Thread.current[:familia_request_cache] = nil if Thread.current[:familia_request_cache]
165
+ Fiber[:familia_request_cache] = nil if Fiber[:familia_request_cache]
166
166
  # With secure-by-default, field access returns ConcealedString
167
167
  model.persistent_data.to_s
168
168
  #=> '[CONCEALED]'
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  ## Multiple encryptions produce unique nonces (concealed behavior)
22
22
  model = NonceTest.new(id: 'nonce-test')
23
- concealed_values = Set.new
23
+ concealed_values = ::Set.new
24
24
 
25
25
  10.times do
26
26
  model.secret = 'same-value'
@@ -190,11 +190,11 @@ end
190
190
 
191
191
  ## JSON serialization prevents leakage by raising error
192
192
  begin
193
- user_json = {
193
+ user_json = Familia::JsonSerializer.dump({
194
194
  id: @user.id,
195
195
  username: @user.username,
196
196
  password: @user.password_hash
197
- }.to_json
197
+ })
198
198
  false
199
199
  rescue Familia::SerializerError
200
200
  true
@@ -203,11 +203,11 @@ end
203
203
 
204
204
  ## JSON serialization with ConcealedString raises error
205
205
  begin
206
- user_json = {
206
+ user_json = Familia::JsonSerializer.dump({
207
207
  id: @user.id,
208
208
  username: @user.username,
209
209
  password: @user.password_hash
210
- }.to_json
210
+ })
211
211
  false
212
212
  rescue Familia::SerializerError => e
213
213
  e.message.include?("ConcealedString")
@@ -294,7 +294,7 @@ api_response = {
294
294
  }
295
295
 
296
296
  begin
297
- @response_json = api_response.to_json
297
+ @response_json = Familia::JsonSerializer.dump(api_response)
298
298
  false
299
299
  rescue Familia::SerializerError
300
300
  true
@@ -311,7 +311,7 @@ api_response = {
311
311
  }
312
312
 
313
313
  begin
314
- @response_json = api_response.to_json
314
+ @response_json = Familia::JsonSerializer.dump(api_response)
315
315
  false
316
316
  rescue Familia::SerializerError
317
317
  true
@@ -328,7 +328,7 @@ api_response = {
328
328
  }
329
329
 
330
330
  begin
331
- @response_json = api_response.to_json
331
+ @response_json = Familia::JsonSerializer.dump(api_response)
332
332
  false
333
333
  rescue Familia::SerializerError => e
334
334
  e.message.include?("ConcealedString")
@@ -69,7 +69,7 @@ hash_result.keys.include?("api_token")
69
69
 
70
70
  ## JSON serialization - to_json (fails for security)
71
71
  begin
72
- @record.api_token.to_json
72
+ Familia::JsonSerializer.dump(@record.api_token)
73
73
  raise "Should have raised SerializerError"
74
74
  rescue Familia::SerializerError => e
75
75
  e.class
@@ -96,7 +96,7 @@ end
96
96
  @record.api_token.to_f
97
97
  #=!> NoMethodError
98
98
 
99
- ## Complex nested JSON structure
99
+ ## Nested JSON with ConcealedString raises error
100
100
  @nested_data = {
101
101
  record: @record,
102
102
  fields: {
@@ -104,23 +104,16 @@ end
104
104
  encrypted: [@record.api_token, @record.secret_notes]
105
105
  }
106
106
  }
107
+ Familia::JsonSerializer.dump(@nested_data)
108
+ #=!> Familia::SerializerError
109
+ #==> error.message.include?("Failed to dump")
107
110
 
108
- begin
109
- @serialized = @nested_data.to_json
110
- false
111
- rescue Familia::SerializerError
112
- true
113
- end
114
- #=> true
115
-
116
- ## Nested JSON with ConcealedString raises error
117
- begin
118
- @nested_data.to_json
119
- false
120
- rescue Familia::SerializerError => e
121
- e.message.include?("ConcealedString cannot be serialized")
122
- end
123
- #=> true
111
+ ## Nested JSON with ConcealedString raises error, plus
112
+ ## Oj strict mode prevents serialization of custom objects
113
+ Familia::JsonSerializer.dump(@record)
114
+ #=:> Familia::SerializerError
115
+ #==> error.message.include?("Failed to dump")
116
+ #==> error.message.include?("strict mode")
124
117
 
125
118
  ## Array of mixed field types safety
126
119
  @mixed_array = [
@@ -131,7 +124,7 @@ end
131
124
  ]
132
125
 
133
126
  begin
134
- @mixed_array.to_json
127
+ Familia::JsonSerializer.dump(@mixed_array)
135
128
  false
136
129
  rescue Familia::SerializerError
137
130
  true
@@ -140,7 +133,7 @@ end
140
133
 
141
134
  ## Mixed array with ConcealedString raises error
142
135
  begin
143
- @mixed_array.to_json
136
+ Familia::JsonSerializer.dump(@mixed_array)
144
137
  false
145
138
  rescue Familia::SerializerError => e
146
139
  e.message.include?("ConcealedString")
@@ -210,7 +210,7 @@ bug_test_obj.extid
210
210
  delete_test_obj = ExternalIdTest.new(id: 'delete_test', name: 'Delete Test')
211
211
  delete_test_obj.save
212
212
  test_extid = delete_test_obj.extid
213
- # Delete the object directly from Redis to simulate cleanup scenario
213
+ # Delete the object directly from Valkey/Redis to simulate cleanup scenario
214
214
  ExternalIdTest.dbclient.del(delete_test_obj.dbkey)
215
215
  # Now try to find by extid - this should clean up mapping and return nil
216
216
  ExternalIdTest.find_by_extid(test_extid)