real_data_tests 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -2
- data/Gemfile.lock +220 -0
- data/README.md +104 -20
- data/lib/real_data_tests/configuration.rb +87 -68
- data/lib/real_data_tests/data_anonymizer.rb +4 -10
- data/lib/real_data_tests/engine.rb +0 -6
- data/lib/real_data_tests/pg_dump_generator.rb +8 -2
- data/lib/real_data_tests/test_data_builder.rb +4 -6
- data/lib/real_data_tests/version.rb +1 -1
- data/lib/real_data_tests.rb +21 -8
- metadata +17 -3
- data/sig/real_data_tests.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8d2dd0beed227113467c6660eaa2101c603ef7c127ad3bd7fb19c835a641b1b
|
4
|
+
data.tar.gz: ac0e5fdb541110b7a523a6fa8a894d49d1c10ab9eeab86003f9ae13ebbf1968a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e048bb72149153484dc179e7628ea3fc41e083906e7cbbd8c1a970b147aa1a232c5d32a6d26553de22c8c3b21a281913515bf1c51b43700c80ba0ac3dc35ffe6
|
7
|
+
data.tar.gz: 3859be802e7aea039691af38cc309bb56c81832bb5740d510e9f6438d10873a66d1efa8aae167a8cdc2a7e3f24b8b4c26f92d2481303d948797bbbd6a5d08d4d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.1
|
3
|
+
## [0.2.1] - 2025-01-13
|
4
|
+
### Fixed
|
5
|
+
- Fixed JSONB field handling to output '{}' instead of empty string for blank values
|
6
|
+
- Added test coverage for JSONB field handling in PgDumpGenerator
|
7
|
+
|
8
|
+
## [0.2.0] - 2025-01-13
|
9
|
+
### Added
|
10
|
+
- New preset system for managing different test data configurations
|
11
|
+
- Added `preset`, `use_preset`, and `with_preset` methods for configuration
|
12
|
+
- Support for multiple named configuration presets
|
13
|
+
- Added documentation for using presets
|
14
|
+
- New PresetConfig class to handle preset-specific configurations
|
4
15
|
|
5
|
-
|
16
|
+
### Changed
|
17
|
+
- Refactored Configuration class to use preset-based approach
|
18
|
+
- Moved configuration methods into PresetConfig class
|
19
|
+
- Updated documentation with preset usage examples and best practices
|
20
|
+
|
21
|
+
## [0.1.0] - 2025-01-11
|
22
|
+
- Initial release
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
real_data_tests (0.2.0)
|
5
|
+
activerecord (>= 5.0)
|
6
|
+
faker (~> 3.0)
|
7
|
+
pg (>= 1.1)
|
8
|
+
rails (>= 5.0)
|
9
|
+
thor (~> 1.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
actioncable (7.2.2.1)
|
15
|
+
actionpack (= 7.2.2.1)
|
16
|
+
activesupport (= 7.2.2.1)
|
17
|
+
nio4r (~> 2.0)
|
18
|
+
websocket-driver (>= 0.6.1)
|
19
|
+
zeitwerk (~> 2.6)
|
20
|
+
actionmailbox (7.2.2.1)
|
21
|
+
actionpack (= 7.2.2.1)
|
22
|
+
activejob (= 7.2.2.1)
|
23
|
+
activerecord (= 7.2.2.1)
|
24
|
+
activestorage (= 7.2.2.1)
|
25
|
+
activesupport (= 7.2.2.1)
|
26
|
+
mail (>= 2.8.0)
|
27
|
+
actionmailer (7.2.2.1)
|
28
|
+
actionpack (= 7.2.2.1)
|
29
|
+
actionview (= 7.2.2.1)
|
30
|
+
activejob (= 7.2.2.1)
|
31
|
+
activesupport (= 7.2.2.1)
|
32
|
+
mail (>= 2.8.0)
|
33
|
+
rails-dom-testing (~> 2.2)
|
34
|
+
actionpack (7.2.2.1)
|
35
|
+
actionview (= 7.2.2.1)
|
36
|
+
activesupport (= 7.2.2.1)
|
37
|
+
nokogiri (>= 1.8.5)
|
38
|
+
racc
|
39
|
+
rack (>= 2.2.4, < 3.2)
|
40
|
+
rack-session (>= 1.0.1)
|
41
|
+
rack-test (>= 0.6.3)
|
42
|
+
rails-dom-testing (~> 2.2)
|
43
|
+
rails-html-sanitizer (~> 1.6)
|
44
|
+
useragent (~> 0.16)
|
45
|
+
actiontext (7.2.2.1)
|
46
|
+
actionpack (= 7.2.2.1)
|
47
|
+
activerecord (= 7.2.2.1)
|
48
|
+
activestorage (= 7.2.2.1)
|
49
|
+
activesupport (= 7.2.2.1)
|
50
|
+
globalid (>= 0.6.0)
|
51
|
+
nokogiri (>= 1.8.5)
|
52
|
+
actionview (7.2.2.1)
|
53
|
+
activesupport (= 7.2.2.1)
|
54
|
+
builder (~> 3.1)
|
55
|
+
erubi (~> 1.11)
|
56
|
+
rails-dom-testing (~> 2.2)
|
57
|
+
rails-html-sanitizer (~> 1.6)
|
58
|
+
activejob (7.2.2.1)
|
59
|
+
activesupport (= 7.2.2.1)
|
60
|
+
globalid (>= 0.3.6)
|
61
|
+
activemodel (7.2.2.1)
|
62
|
+
activesupport (= 7.2.2.1)
|
63
|
+
activerecord (7.2.2.1)
|
64
|
+
activemodel (= 7.2.2.1)
|
65
|
+
activesupport (= 7.2.2.1)
|
66
|
+
timeout (>= 0.4.0)
|
67
|
+
activestorage (7.2.2.1)
|
68
|
+
actionpack (= 7.2.2.1)
|
69
|
+
activejob (= 7.2.2.1)
|
70
|
+
activerecord (= 7.2.2.1)
|
71
|
+
activesupport (= 7.2.2.1)
|
72
|
+
marcel (~> 1.0)
|
73
|
+
activesupport (7.2.2.1)
|
74
|
+
base64
|
75
|
+
benchmark (>= 0.3)
|
76
|
+
bigdecimal
|
77
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
78
|
+
connection_pool (>= 2.2.5)
|
79
|
+
drb
|
80
|
+
i18n (>= 1.6, < 2)
|
81
|
+
logger (>= 1.4.2)
|
82
|
+
minitest (>= 5.1)
|
83
|
+
securerandom (>= 0.3)
|
84
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
85
|
+
base64 (0.2.0)
|
86
|
+
benchmark (0.4.0)
|
87
|
+
bigdecimal (3.1.9)
|
88
|
+
builder (3.3.0)
|
89
|
+
concurrent-ruby (1.3.4)
|
90
|
+
connection_pool (2.5.0)
|
91
|
+
crass (1.0.6)
|
92
|
+
database_cleaner (2.1.0)
|
93
|
+
database_cleaner-active_record (>= 2, < 3)
|
94
|
+
database_cleaner-active_record (2.2.0)
|
95
|
+
activerecord (>= 5.a)
|
96
|
+
database_cleaner-core (~> 2.0.0)
|
97
|
+
database_cleaner-core (2.0.1)
|
98
|
+
date (3.4.1)
|
99
|
+
diff-lcs (1.5.1)
|
100
|
+
drb (2.2.1)
|
101
|
+
erubi (1.13.1)
|
102
|
+
faker (3.5.1)
|
103
|
+
i18n (>= 1.8.11, < 2)
|
104
|
+
globalid (1.2.1)
|
105
|
+
activesupport (>= 6.1)
|
106
|
+
i18n (1.14.6)
|
107
|
+
concurrent-ruby (~> 1.0)
|
108
|
+
io-console (0.8.0)
|
109
|
+
irb (1.14.3)
|
110
|
+
rdoc (>= 4.0.0)
|
111
|
+
reline (>= 0.4.2)
|
112
|
+
logger (1.6.5)
|
113
|
+
loofah (2.24.0)
|
114
|
+
crass (~> 1.0.2)
|
115
|
+
nokogiri (>= 1.12.0)
|
116
|
+
mail (2.8.1)
|
117
|
+
mini_mime (>= 0.1.1)
|
118
|
+
net-imap
|
119
|
+
net-pop
|
120
|
+
net-smtp
|
121
|
+
marcel (1.0.4)
|
122
|
+
mini_mime (1.1.5)
|
123
|
+
minitest (5.25.4)
|
124
|
+
net-imap (0.5.5)
|
125
|
+
date
|
126
|
+
net-protocol
|
127
|
+
net-pop (0.1.2)
|
128
|
+
net-protocol
|
129
|
+
net-protocol (0.2.2)
|
130
|
+
timeout
|
131
|
+
net-smtp (0.5.0)
|
132
|
+
net-protocol
|
133
|
+
nio4r (2.7.4)
|
134
|
+
nokogiri (1.18.1-x86_64-darwin)
|
135
|
+
racc (~> 1.4)
|
136
|
+
pg (1.5.9)
|
137
|
+
psych (5.2.2)
|
138
|
+
date
|
139
|
+
stringio
|
140
|
+
racc (1.8.1)
|
141
|
+
rack (3.1.8)
|
142
|
+
rack-session (2.1.0)
|
143
|
+
base64 (>= 0.1.0)
|
144
|
+
rack (>= 3.0.0)
|
145
|
+
rack-test (2.2.0)
|
146
|
+
rack (>= 1.3)
|
147
|
+
rackup (2.2.1)
|
148
|
+
rack (>= 3)
|
149
|
+
rails (7.2.2.1)
|
150
|
+
actioncable (= 7.2.2.1)
|
151
|
+
actionmailbox (= 7.2.2.1)
|
152
|
+
actionmailer (= 7.2.2.1)
|
153
|
+
actionpack (= 7.2.2.1)
|
154
|
+
actiontext (= 7.2.2.1)
|
155
|
+
actionview (= 7.2.2.1)
|
156
|
+
activejob (= 7.2.2.1)
|
157
|
+
activemodel (= 7.2.2.1)
|
158
|
+
activerecord (= 7.2.2.1)
|
159
|
+
activestorage (= 7.2.2.1)
|
160
|
+
activesupport (= 7.2.2.1)
|
161
|
+
bundler (>= 1.15.0)
|
162
|
+
railties (= 7.2.2.1)
|
163
|
+
rails-dom-testing (2.2.0)
|
164
|
+
activesupport (>= 5.0.0)
|
165
|
+
minitest
|
166
|
+
nokogiri (>= 1.6)
|
167
|
+
rails-html-sanitizer (1.6.2)
|
168
|
+
loofah (~> 2.21)
|
169
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
170
|
+
railties (7.2.2.1)
|
171
|
+
actionpack (= 7.2.2.1)
|
172
|
+
activesupport (= 7.2.2.1)
|
173
|
+
irb (~> 1.13)
|
174
|
+
rackup (>= 1.0.0)
|
175
|
+
rake (>= 12.2)
|
176
|
+
thor (~> 1.0, >= 1.2.2)
|
177
|
+
zeitwerk (~> 2.6)
|
178
|
+
rake (13.2.1)
|
179
|
+
rdoc (6.10.0)
|
180
|
+
psych (>= 4.0.0)
|
181
|
+
reline (0.6.0)
|
182
|
+
io-console (~> 0.5)
|
183
|
+
rspec (3.13.0)
|
184
|
+
rspec-core (~> 3.13.0)
|
185
|
+
rspec-expectations (~> 3.13.0)
|
186
|
+
rspec-mocks (~> 3.13.0)
|
187
|
+
rspec-core (3.13.2)
|
188
|
+
rspec-support (~> 3.13.0)
|
189
|
+
rspec-expectations (3.13.3)
|
190
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
191
|
+
rspec-support (~> 3.13.0)
|
192
|
+
rspec-mocks (3.13.2)
|
193
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
194
|
+
rspec-support (~> 3.13.0)
|
195
|
+
rspec-support (3.13.2)
|
196
|
+
securerandom (0.4.1)
|
197
|
+
stringio (3.1.2)
|
198
|
+
thor (1.3.2)
|
199
|
+
timeout (0.4.3)
|
200
|
+
tzinfo (2.0.6)
|
201
|
+
concurrent-ruby (~> 1.0)
|
202
|
+
useragent (0.16.11)
|
203
|
+
websocket-driver (0.7.7)
|
204
|
+
base64
|
205
|
+
websocket-extensions (>= 0.1.0)
|
206
|
+
websocket-extensions (0.1.5)
|
207
|
+
zeitwerk (2.6.18)
|
208
|
+
|
209
|
+
PLATFORMS
|
210
|
+
x86_64-darwin-21
|
211
|
+
|
212
|
+
DEPENDENCIES
|
213
|
+
database_cleaner (~> 2.0)
|
214
|
+
database_cleaner-active_record
|
215
|
+
rake (~> 13.0)
|
216
|
+
real_data_tests!
|
217
|
+
rspec (~> 3.0)
|
218
|
+
|
219
|
+
BUNDLED WITH
|
220
|
+
2.3.27
|
data/README.md
CHANGED
@@ -50,31 +50,115 @@ Rails.application.config.after_initialize do
|
|
50
50
|
# Directory where SQL dumps will be stored
|
51
51
|
config.dump_path = 'spec/fixtures/real_data_dumps'
|
52
52
|
|
53
|
-
#
|
54
|
-
config.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
53
|
+
# Define a preset for collecting patient visit data
|
54
|
+
config.preset :patient_visits do |p|
|
55
|
+
p.include_associations(
|
56
|
+
:visit_note_type,
|
57
|
+
:patient_status
|
58
|
+
)
|
59
|
+
|
60
|
+
p.include_associations_for 'Patient',
|
61
|
+
:visit_notes,
|
62
|
+
:treatment_reports
|
63
|
+
|
64
|
+
p.prevent_reciprocal 'VisitNoteType.visit_notes'
|
65
|
+
|
66
|
+
p.anonymize 'Patient', {
|
67
|
+
first_name: -> (_) { Faker::Name.first_name },
|
68
|
+
last_name: -> (_) { Faker::Name.last_name }
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Define a preset for organization structure
|
73
|
+
config.preset :org_structure do |p|
|
74
|
+
p.include_associations(
|
75
|
+
:organization,
|
76
|
+
:user
|
77
|
+
)
|
78
|
+
|
79
|
+
p.include_associations_for 'Department',
|
80
|
+
:employees,
|
81
|
+
:managers
|
82
|
+
|
83
|
+
p.limit_association 'Department.employees', 100
|
84
|
+
|
85
|
+
p.anonymize 'User', {
|
86
|
+
email: -> (user) { Faker::Internet.email(name: "user#{user.id}") }
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
## Using Presets
|
94
|
+
|
95
|
+
Real Data Tests allows you to define multiple configuration presets for different data extraction needs. This is particularly useful when you need different association rules and anonymization settings for different testing scenarios.
|
96
|
+
|
97
|
+
### Defining Presets
|
98
|
+
|
99
|
+
You can define presets in your configuration:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
RealDataTests.configure do |config|
|
103
|
+
# Define a preset for patient data
|
104
|
+
config.preset :patient_data do |p|
|
105
|
+
p.include_associations(:patient_status, :visit_note_type)
|
106
|
+
p.include_associations_for 'Patient', :visit_notes
|
107
|
+
p.limit_association 'Patient.visit_notes', 10
|
108
|
+
end
|
109
|
+
|
110
|
+
# Define another preset for billing data
|
111
|
+
config.preset :billing_data do |p|
|
112
|
+
p.include_associations(:payment_method, :insurance_provider)
|
113
|
+
p.include_associations_for 'Invoice', :line_items, :payments
|
114
|
+
p.anonymize 'PaymentMethod', {
|
115
|
+
account_number: -> (_) { Faker::Finance.credit_card }
|
73
116
|
}
|
74
117
|
end
|
75
118
|
end
|
76
119
|
```
|
77
120
|
|
121
|
+
### Using Presets in Your Code
|
122
|
+
|
123
|
+
You can use presets in several ways:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# Create dump file using a specific preset
|
127
|
+
RealDataTests.with_preset(:patient_data) do
|
128
|
+
RealDataTests.create_dump_file(patient, name: "patient_with_visits")
|
129
|
+
end
|
130
|
+
|
131
|
+
# Switch to a different preset
|
132
|
+
RealDataTests.use_preset(:billing_data)
|
133
|
+
RealDataTests.create_dump_file(invoice, name: "invoice_with_payments")
|
134
|
+
|
135
|
+
# Use in tests
|
136
|
+
RSpec.describe "Patient Visits" do
|
137
|
+
it "loads visit data correctly" do
|
138
|
+
RealDataTests.with_preset(:patient_data) do
|
139
|
+
load_real_test_data("patient_with_visits")
|
140
|
+
# Your test code here
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
### Benefits of Using Presets
|
147
|
+
|
148
|
+
- **Organized Configuration**: Keep related association rules and anonymization settings together
|
149
|
+
- **Reusability**: Define configurations once and reuse them across different tests
|
150
|
+
- **Clarity**: Make it clear what data is being extracted for each testing scenario
|
151
|
+
- **Flexibility**: Easily switch between different data extraction rules
|
152
|
+
- **Maintainability**: Update all related settings in one place
|
153
|
+
|
154
|
+
### Best Practices for Presets
|
155
|
+
|
156
|
+
1. **Descriptive Names**: Use clear, purpose-indicating names for your presets
|
157
|
+
2. **Single Responsibility**: Each preset should focus on a specific testing scenario
|
158
|
+
3. **Documentation**: Comment your presets to explain their purpose and usage
|
159
|
+
4. **Composition**: Group related models and their associations in the same preset
|
160
|
+
5. **Version Control**: Keep preset definitions with your test code for easy reference
|
161
|
+
|
78
162
|
## Usage
|
79
163
|
|
80
164
|
### 1. Preparing Test Data
|
@@ -1,53 +1,75 @@
|
|
1
|
-
# lib/real_data_tests/configuration.rb
|
2
1
|
module RealDataTests
|
3
2
|
class Configuration
|
4
|
-
attr_accessor :dump_path, :
|
5
|
-
attr_reader :
|
6
|
-
:model_specific_associations, :association_limits, :prevent_reciprocal_loading
|
3
|
+
attr_accessor :dump_path, :current_preset
|
4
|
+
attr_reader :presets
|
7
5
|
|
8
6
|
def initialize
|
9
7
|
@dump_path = 'spec/fixtures/real_data_dumps'
|
10
|
-
@
|
11
|
-
@
|
12
|
-
|
13
|
-
@model_specific_associations = {}
|
14
|
-
@cleanup_models = []
|
15
|
-
@association_limits = {}
|
16
|
-
@prevent_reciprocal_loading = {}
|
8
|
+
@presets = {}
|
9
|
+
@current_preset = nil
|
10
|
+
create_preset(:default) # Always have a default preset
|
17
11
|
end
|
18
12
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
return
|
24
|
-
end
|
25
|
-
end
|
13
|
+
private def create_preset(name)
|
14
|
+
@presets[name] = PresetConfig.new
|
15
|
+
@current_preset = @presets[name]
|
16
|
+
end
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
@anonymization_rules[model_name.to_s] = mappings
|
30
|
-
rescue => e
|
31
|
-
warn "Note: Anonymization for #{model_name} will be configured when Rails is fully initialized."
|
32
|
-
end
|
18
|
+
def get_association_limit(record_class, association_name)
|
19
|
+
current_preset&.get_association_limit(record_class, association_name)
|
33
20
|
end
|
34
21
|
|
35
|
-
def
|
36
|
-
|
22
|
+
def prevent_reciprocal?(record_class, association_name)
|
23
|
+
current_preset&.prevent_reciprocal?(record_class, association_name)
|
24
|
+
end
|
37
25
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@
|
26
|
+
def preset(name, &block)
|
27
|
+
name = name.to_sym
|
28
|
+
@presets[name] = PresetConfig.new
|
29
|
+
@current_preset = @presets[name]
|
30
|
+
yield(@current_preset) if block_given?
|
31
|
+
@current_preset = @presets[:default]
|
42
32
|
end
|
43
33
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
34
|
+
def use_preset(name)
|
35
|
+
name = name.to_sym
|
36
|
+
raise Error, "Preset '#{name}' not found" unless @presets.key?(name)
|
37
|
+
@current_preset = @presets[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_preset(name)
|
41
|
+
previous_preset = @current_preset
|
42
|
+
use_preset(name)
|
43
|
+
yield if block_given?
|
44
|
+
ensure
|
45
|
+
@current_preset = previous_preset
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(method_name, *args, &block)
|
49
|
+
if @current_preset.respond_to?(method_name)
|
50
|
+
@current_preset.public_send(method_name, *args, &block)
|
51
|
+
else
|
52
|
+
super
|
47
53
|
end
|
54
|
+
end
|
48
55
|
|
49
|
-
|
50
|
-
@
|
56
|
+
def respond_to_missing?(method_name, include_private = false)
|
57
|
+
@current_preset.respond_to?(method_name) || super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class PresetConfig
|
62
|
+
attr_reader :association_filter_mode, :association_filter_list,
|
63
|
+
:model_specific_associations, :association_limits,
|
64
|
+
:prevent_reciprocal_loading, :anonymization_rules
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@association_filter_mode = nil
|
68
|
+
@association_filter_list = []
|
69
|
+
@model_specific_associations = {}
|
70
|
+
@association_limits = {}
|
71
|
+
@prevent_reciprocal_loading = {}
|
72
|
+
@anonymization_rules = {}
|
51
73
|
end
|
52
74
|
|
53
75
|
def include_associations(*associations)
|
@@ -58,21 +80,48 @@ module RealDataTests
|
|
58
80
|
@association_filter_list = associations.flatten
|
59
81
|
end
|
60
82
|
|
61
|
-
|
83
|
+
def exclude_associations(*associations)
|
84
|
+
if @association_filter_mode == :whitelist
|
85
|
+
raise Error, "Cannot set excluded_associations when included_associations is already set"
|
86
|
+
end
|
87
|
+
@association_filter_mode = :blacklist
|
88
|
+
@association_filter_list = associations.flatten
|
89
|
+
end
|
90
|
+
|
62
91
|
def include_associations_for(model, *associations)
|
63
92
|
model_name = model.is_a?(String) ? model : model.name
|
64
93
|
@model_specific_associations[model_name] = associations.flatten
|
65
94
|
end
|
66
95
|
|
96
|
+
def limit_association(path, limit)
|
97
|
+
@association_limits[path.to_s] = limit
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_association_limit(record_class, association_name)
|
101
|
+
path = "#{record_class.name}.#{association_name}"
|
102
|
+
@association_limits[path]
|
103
|
+
end
|
104
|
+
|
105
|
+
def prevent_reciprocal?(record_class, association_name)
|
106
|
+
path = "#{record_class.name}.#{association_name}"
|
107
|
+
@prevent_reciprocal_loading[path]
|
108
|
+
end
|
109
|
+
|
110
|
+
def prevent_reciprocal(path)
|
111
|
+
@prevent_reciprocal_loading[path.to_s] = true
|
112
|
+
end
|
113
|
+
|
114
|
+
def anonymize(model_name, mappings = {})
|
115
|
+
@anonymization_rules[model_name.to_s] = mappings
|
116
|
+
end
|
117
|
+
|
67
118
|
def should_process_association?(model, association_name)
|
68
119
|
model_name = model.is_a?(Class) ? model.name : model.class.name
|
69
120
|
|
70
|
-
# Check model-specific rules first
|
71
121
|
if @model_specific_associations.key?(model_name)
|
72
122
|
return @model_specific_associations[model_name].include?(association_name)
|
73
123
|
end
|
74
124
|
|
75
|
-
# Fall back to global rules
|
76
125
|
case @association_filter_mode
|
77
126
|
when :whitelist
|
78
127
|
@association_filter_list.include?(association_name)
|
@@ -82,35 +131,5 @@ module RealDataTests
|
|
82
131
|
true
|
83
132
|
end
|
84
133
|
end
|
85
|
-
|
86
|
-
def configure_cleanup(*models)
|
87
|
-
@cleanup_models = models.flatten
|
88
|
-
end
|
89
|
-
|
90
|
-
# Configure limits for specific associations
|
91
|
-
# Example: limit_association 'Patient.visit_notes', 10
|
92
|
-
def limit_association(path, limit)
|
93
|
-
@association_limits[path.to_s] = limit
|
94
|
-
end
|
95
|
-
|
96
|
-
# Prevent loading reciprocal associations
|
97
|
-
# Example: prevent_reciprocal 'VisitNoteType.visit_notes'
|
98
|
-
def prevent_reciprocal(path)
|
99
|
-
@prevent_reciprocal_loading[path.to_s] = true
|
100
|
-
end
|
101
|
-
|
102
|
-
# Get limit for a specific association
|
103
|
-
def get_association_limit(record_class, association_name)
|
104
|
-
path = "#{record_class.name}.#{association_name}"
|
105
|
-
@association_limits[path]
|
106
|
-
end
|
107
|
-
|
108
|
-
# Check if reciprocal loading should be prevented
|
109
|
-
def prevent_reciprocal?(record_class, association_name)
|
110
|
-
path = "#{record_class.name}.#{association_name}"
|
111
|
-
@prevent_reciprocal_loading[path]
|
112
|
-
end
|
113
134
|
end
|
114
|
-
|
115
|
-
class Error < StandardError; end
|
116
135
|
end
|
@@ -3,8 +3,8 @@ require 'faker'
|
|
3
3
|
|
4
4
|
module RealDataTests
|
5
5
|
class DataAnonymizer
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(preset_config)
|
7
|
+
@preset_config = preset_config
|
8
8
|
end
|
9
9
|
|
10
10
|
def anonymize_records(records)
|
@@ -16,38 +16,32 @@ module RealDataTests
|
|
16
16
|
def anonymize_record(record)
|
17
17
|
return record unless should_anonymize?(record)
|
18
18
|
|
19
|
-
anonymization_rules = @
|
20
|
-
|
19
|
+
anonymization_rules = @preset_config.anonymization_rules[record.class.name]
|
21
20
|
anonymization_rules.each do |attribute, anonymizer|
|
22
21
|
begin
|
23
22
|
new_value = case anonymizer
|
24
23
|
when String
|
25
|
-
# Handle legacy string-based Faker calls
|
26
24
|
process_faker_string(anonymizer)
|
27
25
|
when Proc, Lambda
|
28
|
-
# Handle lambda-based anonymizers
|
29
26
|
anonymizer.call(record)
|
30
27
|
else
|
31
28
|
raise Error, "Unsupported anonymizer type: #{anonymizer.class}"
|
32
29
|
end
|
33
|
-
|
34
30
|
record.send("#{attribute}=", new_value)
|
35
31
|
rescue => e
|
36
32
|
raise Error, "Failed to anonymize #{attribute} using #{anonymizer.inspect}: #{e.message}"
|
37
33
|
end
|
38
34
|
end
|
39
|
-
|
40
35
|
record
|
41
36
|
end
|
42
37
|
|
43
38
|
private
|
44
39
|
|
45
40
|
def should_anonymize?(record)
|
46
|
-
@
|
41
|
+
@preset_config.anonymization_rules.key?(record.class.name)
|
47
42
|
end
|
48
43
|
|
49
44
|
def process_faker_string(faker_method)
|
50
|
-
# Support legacy string format like "Faker::Name.first_name"
|
51
45
|
faker_class, faker_method = faker_method.split('::')[1..].join('::').split('.')
|
52
46
|
faker_class = Object.const_get("Faker::#{faker_class}")
|
53
47
|
faker_class.send(faker_method)
|
@@ -7,11 +7,5 @@ module RealDataTests
|
|
7
7
|
config.before_configuration do
|
8
8
|
RealDataTests.configuration
|
9
9
|
end
|
10
|
-
|
11
|
-
initializer 'real_data_tests.initialize' do |app|
|
12
|
-
if RealDataTests.configuration
|
13
|
-
RealDataTests.configuration.process_delayed_anonymizations
|
14
|
-
end
|
15
|
-
end
|
16
10
|
end
|
17
11
|
end
|
@@ -138,8 +138,14 @@ module RealDataTests
|
|
138
138
|
value.to_s
|
139
139
|
when :boolean
|
140
140
|
value.to_s
|
141
|
-
when :
|
142
|
-
|
141
|
+
when :jsonb, :json
|
142
|
+
if value.blank?
|
143
|
+
"'{}'" # Return empty JSON object for blank JSONB/JSON fields
|
144
|
+
else
|
145
|
+
sanitize_string(value.is_a?(String) ? value : value.to_json)
|
146
|
+
end
|
147
|
+
when :array
|
148
|
+
parse_and_format_array(value, column_info[:sql_type])
|
143
149
|
else
|
144
150
|
if column_info[:array]
|
145
151
|
parse_and_format_array(value, column_info[:sql_type])
|
@@ -8,19 +8,17 @@ module RealDataTests
|
|
8
8
|
def create_dump_file
|
9
9
|
records = RealDataTests::RecordCollector.new(@record).collect
|
10
10
|
|
11
|
-
# Only anonymize if rules are configured
|
12
|
-
if RealDataTests.configuration.anonymization_rules.any?
|
11
|
+
# Only anonymize if rules are configured in the current preset
|
12
|
+
if RealDataTests.configuration.current_preset.anonymization_rules.any?
|
13
13
|
puts "\nAnonymizing records..."
|
14
|
-
anonymizer = RealDataTests::DataAnonymizer.new(RealDataTests.configuration)
|
15
|
-
anonymizer.anonymize_records(records)
|
14
|
+
anonymizer = RealDataTests::DataAnonymizer.new(RealDataTests.configuration.current_preset)
|
15
|
+
records = anonymizer.anonymize_records(records)
|
16
16
|
end
|
17
17
|
|
18
18
|
dump_content = RealDataTests::PgDumpGenerator.new(records).generate
|
19
19
|
dump_path = dump_file_path
|
20
|
-
|
21
20
|
FileUtils.mkdir_p(File.dirname(dump_path))
|
22
21
|
File.write(dump_path, dump_content)
|
23
|
-
|
24
22
|
puts "\nDump file created at: #{dump_path}"
|
25
23
|
dump_path
|
26
24
|
end
|
data/lib/real_data_tests.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
require 'rails'
|
4
|
+
require_relative 'real_data_tests/version'
|
5
|
+
require_relative 'real_data_tests/configuration'
|
6
|
+
require_relative 'real_data_tests/data_anonymizer'
|
7
|
+
require_relative 'real_data_tests/engine' if defined?(Rails)
|
8
|
+
require_relative 'real_data_tests/pg_dump_generator'
|
9
|
+
require_relative 'real_data_tests/record_collector'
|
10
|
+
require_relative 'real_data_tests/rspec_helper'
|
11
|
+
require_relative 'real_data_tests/test_data_builder'
|
11
12
|
|
12
13
|
module RealDataTests
|
13
14
|
class Error < StandardError; end
|
@@ -28,6 +29,18 @@ module RealDataTests
|
|
28
29
|
@configuration = Configuration.new
|
29
30
|
end
|
30
31
|
|
32
|
+
def use_preset(name)
|
33
|
+
configuration.use_preset(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_preset(name)
|
37
|
+
previous_preset = configuration.current_preset
|
38
|
+
configuration.use_preset(name)
|
39
|
+
yield if block_given?
|
40
|
+
ensure
|
41
|
+
configuration.current_preset = previous_preset
|
42
|
+
end
|
43
|
+
|
31
44
|
def create_dump_file(record, name: nil)
|
32
45
|
raise ConfigurationError, "Configuration not initialized" unless @configuration
|
33
46
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: real_data_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Dias
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: database_cleaner-active_record
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: faker
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,6 +148,7 @@ files:
|
|
134
148
|
- CHANGELOG.md
|
135
149
|
- CODE_OF_CONDUCT.md
|
136
150
|
- Gemfile
|
151
|
+
- Gemfile.lock
|
137
152
|
- LICENSE.txt
|
138
153
|
- README.md
|
139
154
|
- Rakefile
|
@@ -146,7 +161,6 @@ files:
|
|
146
161
|
- lib/real_data_tests/rspec_helper.rb
|
147
162
|
- lib/real_data_tests/test_data_builder.rb
|
148
163
|
- lib/real_data_tests/version.rb
|
149
|
-
- sig/real_data_tests.rbs
|
150
164
|
homepage: https://github.com/diasks2/real_data_tests
|
151
165
|
licenses:
|
152
166
|
- MIT
|
data/sig/real_data_tests.rbs
DELETED