familia 2.0.0.pre14 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +66 -6
- data/CLAUDE.md +1 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +4 -4
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +623 -19
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/{autoloader.rb → features/autoloader.rb} +49 -23
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +68 -66
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +35 -87
- data/lib/familia/features/external_identifier.rb +11 -12
- data/lib/familia/features/object_identifier.rb +58 -20
- data/lib/familia/features/quantization.rb +17 -22
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +69 -271
- data/lib/familia/features/safe_dump.rb +127 -132
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +5 -5
- data/lib/familia/features.rb +21 -21
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -15
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +129 -11
- data/try/core/connection_try.rb +7 -7
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +1 -1
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +433 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- metadata +77 -45
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -228
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/autoloadable.rb +0 -113
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/autoloadable/autoloadable_try.rb +0 -61
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -1,320 +0,0 @@
|
|
1
|
-
# Validation testing for atomic operations and transaction boundaries
|
2
|
-
|
3
|
-
require_relative '../helpers/test_helpers'
|
4
|
-
require_relative '../../lib/familia/validation'
|
5
|
-
|
6
|
-
extend Familia::Validation::TestHelpers
|
7
|
-
|
8
|
-
# Test models for atomic validation
|
9
|
-
class AtomicTestAccount < Familia::Horreum
|
10
|
-
identifier_field :account_id
|
11
|
-
field :account_id
|
12
|
-
field :balance
|
13
|
-
field :status
|
14
|
-
|
15
|
-
def transfer_to(target_account, amount)
|
16
|
-
# This should be atomic - but let's test both atomic and non-atomic versions
|
17
|
-
new_balance = balance.to_i - amount.to_i
|
18
|
-
target_new_balance = target_account.balance.to_i + amount.to_i
|
19
|
-
|
20
|
-
# Non-atomic version (should fail validation)
|
21
|
-
self.balance = new_balance.to_s
|
22
|
-
target_account.balance = target_new_balance.to_s
|
23
|
-
save
|
24
|
-
target_account.save
|
25
|
-
end
|
26
|
-
|
27
|
-
def atomic_transfer_to(target_account, amount)
|
28
|
-
# Proper atomic version using transaction
|
29
|
-
new_balance = balance.to_i - amount.to_i
|
30
|
-
target_new_balance = target_account.balance.to_i + amount.to_i
|
31
|
-
|
32
|
-
transaction do |conn|
|
33
|
-
conn.hset(dbkey, 'balance', new_balance.to_s)
|
34
|
-
conn.hset(target_account.dbkey, 'balance', target_new_balance.to_s)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Update local state
|
38
|
-
self.balance = new_balance.to_s
|
39
|
-
target_account.balance = target_new_balance.to_s
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class AtomicTestCounter < Familia::Horreum
|
44
|
-
identifier_field :name
|
45
|
-
field :name
|
46
|
-
field :value
|
47
|
-
|
48
|
-
def increment_by(amount)
|
49
|
-
transaction do |conn|
|
50
|
-
conn.hincrby(dbkey, 'value', amount)
|
51
|
-
end
|
52
|
-
self.value = (value.to_i + amount).to_s
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
extend Familia::Validation::TestHelpers
|
57
|
-
setup_validation_test
|
58
|
-
|
59
|
-
## Clean up any existing test data
|
60
|
-
cleanup_keys = Familia.dbclient.keys("atomictestaccount:*")
|
61
|
-
cleanup_keys.concat(Familia.dbclient.keys("atomictestcounter:*"))
|
62
|
-
Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
|
63
|
-
true
|
64
|
-
#=> true
|
65
|
-
|
66
|
-
## Transaction expectations validate MULTI/EXEC boundaries
|
67
|
-
validator = Familia::Validation::Validator.new
|
68
|
-
result = validator.validate do |expect|
|
69
|
-
expect.transaction do |tx|
|
70
|
-
tx.hset("atomictestaccount:acc1:object", "balance", "900")
|
71
|
-
.hset("atomictestaccount:acc2:object", "balance", "1100")
|
72
|
-
end
|
73
|
-
|
74
|
-
# Execute atomic transfer
|
75
|
-
acc1 = AtomicTestAccount.new(account_id: "acc1", balance: "1000")
|
76
|
-
acc2 = AtomicTestAccount.new(account_id: "acc2", balance: "1000")
|
77
|
-
acc1.save
|
78
|
-
acc2.save
|
79
|
-
|
80
|
-
acc1.atomic_transfer_to(acc2, 100)
|
81
|
-
end
|
82
|
-
result.valid?
|
83
|
-
#=> true
|
84
|
-
|
85
|
-
## Non-atomic operations should fail strict atomicity validation
|
86
|
-
validator = Familia::Validation::Validator.new(strict_atomicity: true)
|
87
|
-
result = validator.validate do |expect|
|
88
|
-
expect.transaction do |tx|
|
89
|
-
tx.hset("atomictestaccount:acc3:object", "balance", "800")
|
90
|
-
.hset("atomictestaccount:acc4:object", "balance", "1200")
|
91
|
-
end
|
92
|
-
|
93
|
-
# This will NOT use transaction, so should fail
|
94
|
-
acc3 = AtomicTestAccount.new(account_id: "acc3", balance: "1000")
|
95
|
-
acc4 = AtomicTestAccount.new(account_id: "acc4", balance: "1000")
|
96
|
-
acc3.save
|
97
|
-
acc4.save
|
98
|
-
|
99
|
-
acc3.transfer_to(acc4, 200) # Non-atomic version
|
100
|
-
end
|
101
|
-
result.valid?
|
102
|
-
#=> false
|
103
|
-
|
104
|
-
## assert_atomic_operation helper works correctly
|
105
|
-
account_a = AtomicTestAccount.new(account_id: "acc5", balance: "2000")
|
106
|
-
account_b = AtomicTestAccount.new(account_id: "acc6", balance: "500")
|
107
|
-
account_a.save
|
108
|
-
account_b.save
|
109
|
-
|
110
|
-
assert_atomic_operation do |expect|
|
111
|
-
expect.transaction do |tx|
|
112
|
-
tx.hset("atomictestaccount:acc5:object", "balance", "1500")
|
113
|
-
.hset("atomictestaccount:acc6:object", "balance", "1000")
|
114
|
-
end
|
115
|
-
|
116
|
-
account_a.atomic_transfer_to(account_b, 500)
|
117
|
-
end
|
118
|
-
#=> true
|
119
|
-
|
120
|
-
## assert_transaction_used helper works
|
121
|
-
result = assert_transaction_used do
|
122
|
-
counter = AtomicTestCounter.new(name: "test_counter", value: "0")
|
123
|
-
counter.save
|
124
|
-
counter.increment_by(5)
|
125
|
-
end
|
126
|
-
result
|
127
|
-
#=> true
|
128
|
-
|
129
|
-
## assert_no_transaction_used helper works
|
130
|
-
result = assert_no_transaction_used do
|
131
|
-
simple_account = AtomicTestAccount.new(account_id: "simple", balance: "100")
|
132
|
-
simple_account.save # Just a save, no transaction needed
|
133
|
-
end
|
134
|
-
result
|
135
|
-
#=> true
|
136
|
-
|
137
|
-
## Transaction validation detects missing MULTI command
|
138
|
-
commands = []
|
139
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
140
|
-
command: "HSET", args: ["key", "field", "value"],
|
141
|
-
result: "OK", timestamp: Time.now, duration_us: 100
|
142
|
-
)
|
143
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
144
|
-
command: "EXEC", args: [],
|
145
|
-
result: ["OK"], timestamp: Time.now, duration_us: 50
|
146
|
-
)
|
147
|
-
|
148
|
-
sequence = Familia::Validation::CommandRecorder::CommandSequence.new
|
149
|
-
commands.each { |cmd| sequence.add_command(cmd) }
|
150
|
-
|
151
|
-
atomicity_validator = Familia::Validation::AtomicityValidator.new(sequence)
|
152
|
-
result = atomicity_validator.validate
|
153
|
-
result.valid?
|
154
|
-
#=> false
|
155
|
-
|
156
|
-
## Transaction validation detects missing EXEC command
|
157
|
-
commands = []
|
158
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
159
|
-
command: "MULTI", args: [],
|
160
|
-
result: "OK", timestamp: Time.now, duration_us: 100
|
161
|
-
)
|
162
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
163
|
-
command: "HSET", args: ["key", "field", "value"],
|
164
|
-
result: "QUEUED", timestamp: Time.now, duration_us: 50
|
165
|
-
)
|
166
|
-
|
167
|
-
sequence = Familia::Validation::CommandRecorder::CommandSequence.new
|
168
|
-
commands.each { |cmd| sequence.add_command(cmd) }
|
169
|
-
|
170
|
-
atomicity_validator = Familia::Validation::AtomicityValidator.new(sequence)
|
171
|
-
result = atomicity_validator.validate
|
172
|
-
result.valid?
|
173
|
-
#=> false
|
174
|
-
|
175
|
-
## Proper transaction structure passes validation
|
176
|
-
commands = []
|
177
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
178
|
-
command: "MULTI", args: [],
|
179
|
-
result: "OK", timestamp: Time.now, duration_us: 100,
|
180
|
-
context: { transaction: false }
|
181
|
-
)
|
182
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
183
|
-
command: "HSET", args: ["key", "field", "value"],
|
184
|
-
result: "QUEUED", timestamp: Time.now, duration_us: 50,
|
185
|
-
context: { transaction: true }
|
186
|
-
)
|
187
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
188
|
-
command: "INCR", args: ["counter"],
|
189
|
-
result: "QUEUED", timestamp: Time.now, duration_us: 30,
|
190
|
-
context: { transaction: true }
|
191
|
-
)
|
192
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
193
|
-
command: "EXEC", args: [],
|
194
|
-
result: [1, 1], timestamp: Time.now, duration_us: 200,
|
195
|
-
context: { transaction: false }
|
196
|
-
)
|
197
|
-
|
198
|
-
sequence = Familia::Validation::CommandRecorder::CommandSequence.new
|
199
|
-
sequence.start_transaction
|
200
|
-
commands.each { |cmd| sequence.add_command(cmd) }
|
201
|
-
sequence.end_transaction
|
202
|
-
|
203
|
-
atomicity_validator = Familia::Validation::AtomicityValidator.new(sequence)
|
204
|
-
result = atomicity_validator.validate
|
205
|
-
result.valid?
|
206
|
-
#=> true
|
207
|
-
|
208
|
-
## batch_update operations should be atomic
|
209
|
-
validator = Familia::Validation::Validator.new
|
210
|
-
result = validator.validate do |expect|
|
211
|
-
expect.transaction do |tx|
|
212
|
-
tx.hset("atomictestaccount:batch1:object", "balance", "1500")
|
213
|
-
.hset("atomictestaccount:batch1:object", "status", "active")
|
214
|
-
end
|
215
|
-
|
216
|
-
account = AtomicTestAccount.new(account_id: "batch1", balance: "1000", status: "pending")
|
217
|
-
account.save
|
218
|
-
|
219
|
-
# batch_update should use transaction internally
|
220
|
-
account.batch_update(balance: "1500", status: "active")
|
221
|
-
end
|
222
|
-
result.valid?
|
223
|
-
#=> true
|
224
|
-
|
225
|
-
## Nested transaction detection works
|
226
|
-
commands = []
|
227
|
-
# Invalid nested transaction
|
228
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
229
|
-
command: "MULTI", args: [], result: "OK",
|
230
|
-
timestamp: Time.now, duration_us: 100
|
231
|
-
)
|
232
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
233
|
-
command: "MULTI", args: [], result: "ERR",
|
234
|
-
timestamp: Time.now, duration_us: 50
|
235
|
-
)
|
236
|
-
|
237
|
-
sequence = Familia::Validation::CommandRecorder::CommandSequence.new
|
238
|
-
commands.each { |cmd| sequence.add_command(cmd) }
|
239
|
-
|
240
|
-
atomicity_validator = Familia::Validation::AtomicityValidator.new(sequence)
|
241
|
-
result = atomicity_validator.validate
|
242
|
-
result.valid?
|
243
|
-
#=> false
|
244
|
-
|
245
|
-
## Empty transaction detection
|
246
|
-
commands = []
|
247
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
248
|
-
command: "MULTI", args: [], result: "OK",
|
249
|
-
timestamp: Time.now, duration_us: 100
|
250
|
-
)
|
251
|
-
commands << Familia::Validation::CommandRecorder::RecordedCommand.new(
|
252
|
-
command: "EXEC", args: [], result: [],
|
253
|
-
timestamp: Time.now, duration_us: 50
|
254
|
-
)
|
255
|
-
|
256
|
-
sequence = Familia::Validation::CommandRecorder::CommandSequence.new
|
257
|
-
sequence.start_transaction
|
258
|
-
commands.each { |cmd| sequence.add_command(cmd) }
|
259
|
-
sequence.end_transaction
|
260
|
-
|
261
|
-
result = Familia::Validation::AtomicityValidator.new(sequence).validate
|
262
|
-
# Should be valid but with warning about empty transaction
|
263
|
-
result.valid? && result.warning_messages.any?
|
264
|
-
#=> true
|
265
|
-
|
266
|
-
## Real Familia transaction usage validates correctly
|
267
|
-
validator = Familia::Validation::Validator.new
|
268
|
-
result = validator.validate do |expect|
|
269
|
-
expect.transaction do |tx|
|
270
|
-
tx.hset("atomictestcounter:familia_tx:object", "value", "10")
|
271
|
-
end
|
272
|
-
|
273
|
-
counter = AtomicTestCounter.new(name: "familia_tx", value: "5")
|
274
|
-
counter.save
|
275
|
-
|
276
|
-
# Use Familia.transaction directly
|
277
|
-
Familia.transaction do |conn|
|
278
|
-
conn.hset(counter.dbkey, "value", "10")
|
279
|
-
end
|
280
|
-
end
|
281
|
-
result.valid?
|
282
|
-
#=> true
|
283
|
-
|
284
|
-
## Multiple transactions in sequence validate correctly
|
285
|
-
validator = Familia::Validation::Validator.new
|
286
|
-
result = validator.validate do |expect|
|
287
|
-
expect.transaction do |tx|
|
288
|
-
tx.hset("atomictestcounter:multi_tx_1:object", "value", "5")
|
289
|
-
end
|
290
|
-
|
291
|
-
expect.transaction do |tx|
|
292
|
-
tx.hset("atomictestcounter:multi_tx_2:object", "value", "10")
|
293
|
-
end
|
294
|
-
|
295
|
-
counter1 = AtomicTestCounter.new(name: "multi_tx_1", value: "0")
|
296
|
-
counter2 = AtomicTestCounter.new(name: "multi_tx_2", value: "0")
|
297
|
-
counter1.save
|
298
|
-
counter2.save
|
299
|
-
|
300
|
-
# Two separate transactions
|
301
|
-
Familia.transaction do |conn|
|
302
|
-
conn.hset(counter1.dbkey, "value", "5")
|
303
|
-
end
|
304
|
-
|
305
|
-
Familia.transaction do |conn|
|
306
|
-
conn.hset(counter2.dbkey, "value", "10")
|
307
|
-
end
|
308
|
-
end
|
309
|
-
result.valid?
|
310
|
-
#=> true
|
311
|
-
|
312
|
-
## Cleanup test environment
|
313
|
-
teardown_validation_test
|
314
|
-
|
315
|
-
## Clean up test data
|
316
|
-
cleanup_keys = Familia.dbclient.keys("atomictestaccount:*")
|
317
|
-
cleanup_keys.concat(Familia.dbclient.keys("atomictestcounter:*"))
|
318
|
-
Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
|
319
|
-
true
|
320
|
-
#=> true
|
@@ -1,207 +0,0 @@
|
|
1
|
-
# Validation testing for Redis command recording and expectation matching
|
2
|
-
|
3
|
-
require_relative '../helpers/test_helpers'
|
4
|
-
require_relative '../../lib/familia/validation'
|
5
|
-
|
6
|
-
# Test class for validation testing
|
7
|
-
class ValidationTestModel < Familia::Horreum
|
8
|
-
identifier_field :id
|
9
|
-
field :id
|
10
|
-
field :name
|
11
|
-
field :email
|
12
|
-
field :active
|
13
|
-
end
|
14
|
-
|
15
|
-
extend Familia::Validation::TestHelpers
|
16
|
-
|
17
|
-
# Initialize validation framework
|
18
|
-
setup_validation_test
|
19
|
-
|
20
|
-
## Command recorder captures basic Redis commands
|
21
|
-
Familia::Validation::CommandRecorder.start_recording
|
22
|
-
ValidationTestModel.new(id: "test1", name: "John").save
|
23
|
-
commands = Familia::Validation::CommandRecorder.stop_recording
|
24
|
-
commands.command_count > 0
|
25
|
-
#=> true
|
26
|
-
|
27
|
-
## Command recorder captures command details
|
28
|
-
Familia::Validation::CommandRecorder.start_recording
|
29
|
-
Familia.dbclient.hset("test:key", "field", "value")
|
30
|
-
commands = Familia::Validation::CommandRecorder.stop_recording
|
31
|
-
first_command = commands.commands.first
|
32
|
-
first_command ? [first_command.command, first_command.args] : "No commands recorded"
|
33
|
-
#=> ["HSET", ["test:key", "field", "value"]]
|
34
|
-
|
35
|
-
## Expectation DSL allows chaining commands
|
36
|
-
expectations = Familia::Validation::CommandExpectations.new
|
37
|
-
expectations.hset("user:123", "name", "John")
|
38
|
-
.hset("user:123", "email", "john@example.com")
|
39
|
-
.incr("counter")
|
40
|
-
expectations.expected_commands.length
|
41
|
-
#=> 3
|
42
|
-
|
43
|
-
## Basic command validation passes with exact match
|
44
|
-
validator = Familia::Validation::Validator.new
|
45
|
-
result = validator.validate do |expect|
|
46
|
-
expect.hset("validationtestmodel:test2:object", "name", "Jane")
|
47
|
-
.hset("validationtestmodel:test2:object", "id", "test2")
|
48
|
-
|
49
|
-
# Execute the actual operation
|
50
|
-
model = ValidationTestModel.new(id: "test2", name: "Jane")
|
51
|
-
model.save
|
52
|
-
end
|
53
|
-
result.valid?
|
54
|
-
#=> true
|
55
|
-
|
56
|
-
## Command validation fails with wrong command
|
57
|
-
validator = Familia::Validation::Validator.new
|
58
|
-
result = validator.validate do |expect|
|
59
|
-
expect.get("wrong:key") # This won't match actual operation
|
60
|
-
|
61
|
-
ValidationTestModel.new(id: "test3", name: "Bob").save
|
62
|
-
end
|
63
|
-
result.valid?
|
64
|
-
#=> false
|
65
|
-
|
66
|
-
## Command validation provides detailed error messages
|
67
|
-
validator = Familia::Validation::Validator.new
|
68
|
-
result = validator.validate do |expect|
|
69
|
-
expect.hset("wrong:key", "field", "value")
|
70
|
-
|
71
|
-
ValidationTestModel.new(id: "test4").save
|
72
|
-
end
|
73
|
-
result.error_messages.length > 0
|
74
|
-
#=> true
|
75
|
-
|
76
|
-
## Pattern matching works for flexible validation
|
77
|
-
validator = Familia::Validation::Validator.new
|
78
|
-
result = validator.validate do |expect|
|
79
|
-
expect.match_pattern(/^HSET validationtestmodel:test5:object/)
|
80
|
-
|
81
|
-
ValidationTestModel.new(id: "test5", name: "Alice").save
|
82
|
-
end
|
83
|
-
result.valid?
|
84
|
-
#=> true
|
85
|
-
|
86
|
-
## Any value matchers work correctly
|
87
|
-
validator = Familia::Validation::Validator.new
|
88
|
-
result = validator.validate do |expect|
|
89
|
-
expect.hset("validationtestmodel:test6:object", "name", any_string)
|
90
|
-
.hset("validationtestmodel:test6:object", "id", "test6")
|
91
|
-
|
92
|
-
ValidationTestModel.new(id: "test6", name: "Charlie").save
|
93
|
-
end
|
94
|
-
result.valid?
|
95
|
-
#=> true
|
96
|
-
|
97
|
-
## Test helper assert_redis_commands works
|
98
|
-
model = ValidationTestModel.new(id: "test7", name: "Dave")
|
99
|
-
assert_redis_commands do |expect|
|
100
|
-
expect.hset("validationtestmodel:test7:object", "name", "Dave")
|
101
|
-
.hset("validationtestmodel:test7:object", "id", "test7")
|
102
|
-
|
103
|
-
model.save
|
104
|
-
end
|
105
|
-
#=> true
|
106
|
-
|
107
|
-
## Test helper assert_command_count works
|
108
|
-
result = assert_command_count(2) do
|
109
|
-
Familia.dbclient.hset("test:count", "field1", "value1")
|
110
|
-
Familia.dbclient.hset("test:count", "field2", "value2")
|
111
|
-
end
|
112
|
-
result
|
113
|
-
#=> true
|
114
|
-
|
115
|
-
## Test helper assert_no_redis_commands works
|
116
|
-
result = assert_no_redis_commands do
|
117
|
-
# Just create object, don't save
|
118
|
-
ValidationTestModel.new(id: "test8", name: "Eve")
|
119
|
-
end
|
120
|
-
result
|
121
|
-
#=> true
|
122
|
-
|
123
|
-
## Flexible order validation works
|
124
|
-
validator = Familia::Validation::Validator.new
|
125
|
-
result = validator.validate do |expect|
|
126
|
-
expect.strict_order(false)
|
127
|
-
.hset("validationtestmodel:test9:object", "name", any_string)
|
128
|
-
.hset("validationtestmodel:test9:object", "id", "test9")
|
129
|
-
|
130
|
-
# Save in any order should work
|
131
|
-
model = ValidationTestModel.new(id: "test9", name: "Frank")
|
132
|
-
model.save
|
133
|
-
end
|
134
|
-
result.valid?
|
135
|
-
#=> true
|
136
|
-
|
137
|
-
## Command sequence provides useful metadata
|
138
|
-
Familia::Validation::CommandRecorder.start_recording
|
139
|
-
ValidationTestModel.new(id: "test10", name: "Grace").save
|
140
|
-
commands = Familia::Validation::CommandRecorder.stop_recording
|
141
|
-
summary = {
|
142
|
-
command_count: commands.command_count,
|
143
|
-
has_commands: commands.commands.any?,
|
144
|
-
first_command_type: commands.commands.first&.command_type
|
145
|
-
}
|
146
|
-
summary[:command_count] > 0 && summary[:has_commands]
|
147
|
-
#=> true
|
148
|
-
|
149
|
-
## Performance tracking captures timing information
|
150
|
-
validator = Familia::Validation::Validator.new(performance_tracking: true)
|
151
|
-
result = validator.validate do |expect|
|
152
|
-
expect.match_pattern(/HSET/)
|
153
|
-
|
154
|
-
ValidationTestModel.new(id: "test11", name: "Henry").save
|
155
|
-
end
|
156
|
-
result.respond_to?(:performance_metrics) && result.performance_metrics[:total_commands] > 0
|
157
|
-
#=> true
|
158
|
-
|
159
|
-
## Validation result provides comprehensive summary
|
160
|
-
validator = Familia::Validation::Validator.new
|
161
|
-
result = validator.validate do |expect|
|
162
|
-
expect.hset("validationtestmodel:test12:object", "name", "Iris")
|
163
|
-
|
164
|
-
ValidationTestModel.new(id: "test12", name: "Iris").save
|
165
|
-
end
|
166
|
-
summary = result.summary
|
167
|
-
summary[:valid] == true && summary[:expected_commands] == 1
|
168
|
-
#=> true
|
169
|
-
|
170
|
-
## Complex validation with multiple operations
|
171
|
-
class ComplexTestModel < Familia::Horreum
|
172
|
-
identifier_field :id
|
173
|
-
field :id, :name, :email
|
174
|
-
list :tags
|
175
|
-
set :categories
|
176
|
-
end
|
177
|
-
|
178
|
-
validator = Familia::Validation::Validator.new
|
179
|
-
result = validator.validate do |expect|
|
180
|
-
expect.hset("complextestmodel:complex1:object", "name", "Complex")
|
181
|
-
.hset("complextestmodel:complex1:object", "id", "complex1")
|
182
|
-
.lpush("complextestmodel:complex1:tags", "tag1")
|
183
|
-
.sadd("complextestmodel:complex1:categories", "cat1")
|
184
|
-
|
185
|
-
model = ComplexTestModel.new(id: "complex1", name: "Complex")
|
186
|
-
model.save
|
187
|
-
model.tags.unshift("tag1")
|
188
|
-
model.categories.add("cat1")
|
189
|
-
end
|
190
|
-
result.valid?
|
191
|
-
#=> true
|
192
|
-
|
193
|
-
## Cleanup test environment
|
194
|
-
begin
|
195
|
-
teardown_validation_test
|
196
|
-
rescue => e
|
197
|
-
# Gracefully handle teardown issues during test development
|
198
|
-
puts "Teardown warning: #{e.message}"
|
199
|
-
end
|
200
|
-
|
201
|
-
## Clean up test data
|
202
|
-
test_keys = Familia.dbclient.keys("validationtestmodel:*")
|
203
|
-
test_keys.concat(Familia.dbclient.keys("complextestmodel:*"))
|
204
|
-
test_keys.concat(Familia.dbclient.keys("test:*"))
|
205
|
-
Familia.dbclient.del(*test_keys) if test_keys.any?
|
206
|
-
true
|
207
|
-
#=> true
|