synthra 0.1.0
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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Property-Based Testing
|
|
2
|
+
|
|
3
|
+
FakeDataDSL integrates seamlessly with RSpec for property-based testing, allowing you to verify that properties hold for all generated data.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'fake_data_dsl'
|
|
9
|
+
include FakeDataDSL::PropertyTesting
|
|
10
|
+
|
|
11
|
+
RSpec.describe "User Registration" do
|
|
12
|
+
it "accepts all valid users" do
|
|
13
|
+
verify_property("User", count: 1000) do |user_data|
|
|
14
|
+
user = User.create(user_data)
|
|
15
|
+
expect(user).to be_valid
|
|
16
|
+
expect(user.save).to be_truthy
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
### verify_property
|
|
25
|
+
|
|
26
|
+
Verifies a property holds for multiple generated records:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
verify_property("User", count: 100) do |user_data|
|
|
30
|
+
expect(user_data["email"]).to match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
|
31
|
+
expect(user_data["age"]).to be_between(18, 80)
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Options:**
|
|
36
|
+
- `count` - Number of records to generate (default: 100)
|
|
37
|
+
- `seed` - Random seed for reproducibility
|
|
38
|
+
- `mode` - Generation mode (`:random`, `:edge`, `:invalid`, `:hostile`, `:mixed`)
|
|
39
|
+
- `registry` - Custom schema registry
|
|
40
|
+
|
|
41
|
+
### verify_property_stream
|
|
42
|
+
|
|
43
|
+
Memory-efficient streaming verification for large counts:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
verify_property_stream("User", count: 10_000) do |user_data|
|
|
47
|
+
expect(user_data["id"]).to be_a(String)
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Records are generated lazily and garbage collected as they're processed.
|
|
52
|
+
|
|
53
|
+
### shrink_counterexample
|
|
54
|
+
|
|
55
|
+
Shrinks a failing test case to find a minimal counterexample:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
failing_record = schema.generate(mode: :hostile)
|
|
59
|
+
|
|
60
|
+
minimal = shrink_counterexample("User", failing_record) do |record|
|
|
61
|
+
expect(record["age"]).to be > 100 # This will fail
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# minimal contains the smallest record that still fails
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
### Email Format Validation
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
verify_property("User", count: 1000) do |user_data|
|
|
73
|
+
email = user_data["email"]
|
|
74
|
+
expect(email).to match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Age Range Validation
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
verify_property("User", count: 1000) do |user_data|
|
|
82
|
+
age = user_data["age"]
|
|
83
|
+
expect(age).to be_between(18, 80)
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### UUID Format Validation
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
verify_property("User", count: 1000) do |user_data|
|
|
91
|
+
uuid = user_data["id"]
|
|
92
|
+
expect(uuid).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Model Validation
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
verify_property("User", count: 500) do |user_data|
|
|
100
|
+
user = User.new(user_data)
|
|
101
|
+
expect(user).to be_valid
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### API Endpoint Testing
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
verify_property("Order", count: 200) do |order_data|
|
|
109
|
+
response = post("/api/orders", order_data)
|
|
110
|
+
expect(response.status).to eq(201)
|
|
111
|
+
expect(response.json["id"]).to be_present
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Deterministic Testing
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
seed = 12345
|
|
119
|
+
|
|
120
|
+
verify_property("User", count: 100, seed: seed) do |user_data|
|
|
121
|
+
# Same seed = same data every time
|
|
122
|
+
expect(user_data["email"]).to eq(expected_email)
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Error Messages
|
|
127
|
+
|
|
128
|
+
When a property fails, you get detailed error information:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Property failed for record 42/100 (seed: 12345):
|
|
132
|
+
expected 25 to be > 100
|
|
133
|
+
Record: {"id"=>"550e8400...", "age"=>25, ...}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This includes:
|
|
137
|
+
- Record index (which record failed)
|
|
138
|
+
- Seed (for reproducibility)
|
|
139
|
+
- Full error message
|
|
140
|
+
- Complete record data
|
|
141
|
+
|
|
142
|
+
## Best Practices
|
|
143
|
+
|
|
144
|
+
### 1. Use Appropriate Counts
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Quick validation (fast)
|
|
148
|
+
verify_property("User", count: 10) do |user_data|
|
|
149
|
+
expect(user_data).to be_a(Hash)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Comprehensive testing (thorough)
|
|
153
|
+
verify_property("User", count: 1000) do |user_data|
|
|
154
|
+
expect(user_data["email"]).to match(/@/)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. Use Seeds for Reproducibility
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Same seed = same data = reproducible tests
|
|
162
|
+
verify_property("User", count: 100, seed: 42) do |user_data|
|
|
163
|
+
# Test logic
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3. Use Streaming for Large Counts
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# Memory-efficient for 10K+ records
|
|
171
|
+
verify_property_stream("User", count: 50_000) do |user_data|
|
|
172
|
+
expect(user_data["id"]).to be_present
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 4. Test Edge Cases
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# Test with edge case mode
|
|
180
|
+
verify_property("User", count: 100, mode: :edge) do |user_data|
|
|
181
|
+
# Should handle empty strings, nulls, extremes
|
|
182
|
+
expect(user_data["email"]).to be_a(String).or(be_nil)
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 5. Test Invalid Data Handling
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
# Test validation logic
|
|
190
|
+
verify_property("User", count: 100, mode: :invalid) do |user_data|
|
|
191
|
+
# Should reject invalid data
|
|
192
|
+
user = User.new(user_data)
|
|
193
|
+
expect(user).not_to be_valid
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Integration with RSpec
|
|
198
|
+
|
|
199
|
+
The `PropertyTesting` module is automatically included in RSpec when available:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
# No need to include manually
|
|
203
|
+
RSpec.describe "User" do
|
|
204
|
+
it "validates all users" do
|
|
205
|
+
verify_property("User") do |user_data|
|
|
206
|
+
# Test logic
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Advanced Usage
|
|
213
|
+
|
|
214
|
+
### Custom Registry
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
custom_registry = FakeDataDSL::Registry.new
|
|
218
|
+
custom_registry.load_dir("spec/schemas/")
|
|
219
|
+
|
|
220
|
+
verify_property("User", count: 100, registry: custom_registry) do |user_data|
|
|
221
|
+
# Test logic
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Multiple Properties
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
RSpec.describe "User" do
|
|
229
|
+
it "validates email format" do
|
|
230
|
+
verify_property("User", count: 100) do |user_data|
|
|
231
|
+
expect(user_data["email"]).to match(/@/)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "validates age range" do
|
|
236
|
+
verify_property("User", count: 100) do |user_data|
|
|
237
|
+
expect(user_data["age"]).to be_between(18, 80)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Shrinking Counterexamples
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
it "finds minimal failing case" do
|
|
247
|
+
failing_record = {
|
|
248
|
+
"id" => "test",
|
|
249
|
+
"name" => "Test",
|
|
250
|
+
"email" => "test@example.com",
|
|
251
|
+
"age" => 25,
|
|
252
|
+
"optional_field" => "can be removed"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
minimal = shrink_counterexample("User", failing_record) do |record|
|
|
256
|
+
expect(record["age"]).to be > 100
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# minimal may have optional_field removed if it still fails
|
|
260
|
+
expect(minimal["age"]).to be <= 100
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Performance
|
|
265
|
+
|
|
266
|
+
Property-based testing is efficient:
|
|
267
|
+
|
|
268
|
+
- **100 records**: ~0.1s
|
|
269
|
+
- **1,000 records**: ~1s
|
|
270
|
+
- **10,000 records**: ~10s (use streaming)
|
|
271
|
+
|
|
272
|
+
Use `verify_property_stream` for counts > 1,000 to avoid memory issues.
|
|
273
|
+
|
|
274
|
+
## Limitations
|
|
275
|
+
|
|
276
|
+
- Requires RSpec (automatically detected)
|
|
277
|
+
- Shrinking is basic (removes optional fields, simplifies arrays)
|
|
278
|
+
- No automatic property discovery (you must write properties)
|
|
279
|
+
|
|
280
|
+
## See Also
|
|
281
|
+
|
|
282
|
+
- [RSpec Integration Guide](tech_docs/integration/rspec.md)
|
|
283
|
+
- [Generation Modes](tech_docs/modes/overview.md)
|
|
284
|
+
- [API Reference](tech_docs/api/overview.md)
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# gRPC/Protobuf Export
|
|
2
|
+
|
|
3
|
+
Export your FakeDataDSL schemas to **Protocol Buffer definitions** for use with gRPC microservices.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Export single schema
|
|
9
|
+
fake_data_dsl export User -d schemas/ -f protobuf -o user.proto
|
|
10
|
+
|
|
11
|
+
# Export all schemas
|
|
12
|
+
fake_data_dsl export --all -d schemas/ -f protobuf -o all.proto
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Generated Output
|
|
16
|
+
|
|
17
|
+
For this schema:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
User:
|
|
21
|
+
id: uuid
|
|
22
|
+
name: name
|
|
23
|
+
email: email
|
|
24
|
+
age: number(18..65)
|
|
25
|
+
active: boolean
|
|
26
|
+
role: enum(admin, user, moderator)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The exporter generates:
|
|
30
|
+
|
|
31
|
+
```protobuf
|
|
32
|
+
syntax = "proto3";
|
|
33
|
+
|
|
34
|
+
package generated;
|
|
35
|
+
|
|
36
|
+
import "google/protobuf/timestamp.proto";
|
|
37
|
+
|
|
38
|
+
enum UserRole {
|
|
39
|
+
USERROLE_UNSPECIFIED = 0;
|
|
40
|
+
USERROLE_ADMIN = 1;
|
|
41
|
+
USERROLE_USER = 2;
|
|
42
|
+
USERROLE_MODERATOR = 3;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
message User {
|
|
46
|
+
string id = 1;
|
|
47
|
+
string name = 2;
|
|
48
|
+
string email = 3;
|
|
49
|
+
int64 age = 4;
|
|
50
|
+
bool active = 5;
|
|
51
|
+
UserRole role = 6;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
service UserService {
|
|
55
|
+
rpc GetUser(GetUserRequest) returns (User);
|
|
56
|
+
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
|
|
57
|
+
rpc GenerateUser(GenerateUserRequest) returns (User);
|
|
58
|
+
rpc GenerateManyUsers(GenerateManyUsersRequest) returns (stream User);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
message GetUserRequest {
|
|
62
|
+
string id = 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
message ListUsersRequest {
|
|
66
|
+
int32 page = 1;
|
|
67
|
+
int32 page_size = 2;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
message ListUsersResponse {
|
|
71
|
+
repeated User users = 1;
|
|
72
|
+
int32 total_count = 2;
|
|
73
|
+
int32 page = 3;
|
|
74
|
+
int32 page_size = 4;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
message GenerateUserRequest {
|
|
78
|
+
optional int64 seed = 1;
|
|
79
|
+
optional string mode = 2;
|
|
80
|
+
map<string, string> overrides = 3;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
message GenerateManyUsersRequest {
|
|
84
|
+
int32 count = 1;
|
|
85
|
+
optional int64 seed = 2;
|
|
86
|
+
optional string mode = 3;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Ruby API
|
|
91
|
+
|
|
92
|
+
### Single Schema
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
schema = FakeDataDSL.load("schemas/user.dsl")
|
|
96
|
+
|
|
97
|
+
exporter = FakeDataDSL::Export::Protobuf.new(
|
|
98
|
+
schema,
|
|
99
|
+
registry: registry,
|
|
100
|
+
package: "myapp.v1",
|
|
101
|
+
go_package: "github.com/myapp/proto/v1",
|
|
102
|
+
with_service: true
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
proto = exporter.export
|
|
106
|
+
File.write("user.proto", proto)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### All Schemas
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
proto = FakeDataDSL::Export::Protobuf.export_all(
|
|
113
|
+
registry,
|
|
114
|
+
package: "myapp.v1",
|
|
115
|
+
go_package: "github.com/myapp/proto/v1",
|
|
116
|
+
with_service: true
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
File.write("all.proto", proto)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Options
|
|
123
|
+
|
|
124
|
+
| Option | Description | Default |
|
|
125
|
+
|--------|-------------|---------|
|
|
126
|
+
| `package` | Proto package name | "generated" |
|
|
127
|
+
| `go_package` | Go package path | nil |
|
|
128
|
+
| `syntax` | Proto syntax version | "proto3" |
|
|
129
|
+
| `with_service` | Generate gRPC service | true |
|
|
130
|
+
|
|
131
|
+
## Type Mapping
|
|
132
|
+
|
|
133
|
+
| FakeDataDSL | Protobuf |
|
|
134
|
+
|-------------|----------|
|
|
135
|
+
| `text`, `name`, `email`, etc. | `string` |
|
|
136
|
+
| `uuid`, `ulid`, `nanoid` | `string` |
|
|
137
|
+
| `number`, `integer` | `int64` |
|
|
138
|
+
| `int`, `int32` | `int32` |
|
|
139
|
+
| `float`, `double`, `money` | `double` |
|
|
140
|
+
| `boolean` | `bool` |
|
|
141
|
+
| `timestamp`, `datetime` | `google.protobuf.Timestamp` |
|
|
142
|
+
| `date`, `time` | `string` |
|
|
143
|
+
| `binary`, `base64` | `bytes` |
|
|
144
|
+
| `array(T)` | `repeated T` |
|
|
145
|
+
| `enum(a, b, c)` | Custom enum |
|
|
146
|
+
| `SchemaRef` | Message reference |
|
|
147
|
+
|
|
148
|
+
## Arrays
|
|
149
|
+
|
|
150
|
+
Arrays map to `repeated` fields:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
Tags:
|
|
154
|
+
items: array(text, 1..10)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Becomes:
|
|
158
|
+
|
|
159
|
+
```protobuf
|
|
160
|
+
message Tags {
|
|
161
|
+
repeated string items = 1;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Nested Schemas
|
|
166
|
+
|
|
167
|
+
Schema references become message references:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Address:
|
|
171
|
+
street: street
|
|
172
|
+
city: city
|
|
173
|
+
|
|
174
|
+
Person:
|
|
175
|
+
name: name
|
|
176
|
+
address: Address
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Becomes:
|
|
180
|
+
|
|
181
|
+
```protobuf
|
|
182
|
+
message Address {
|
|
183
|
+
string street = 1;
|
|
184
|
+
string city = 2;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
message Person {
|
|
188
|
+
string name = 1;
|
|
189
|
+
Address address = 2;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Enums
|
|
194
|
+
|
|
195
|
+
Enums are extracted and generated separately:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
User:
|
|
199
|
+
role: enum(admin, user, moderator)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Becomes:
|
|
203
|
+
|
|
204
|
+
```protobuf
|
|
205
|
+
enum UserRole {
|
|
206
|
+
USERROLE_UNSPECIFIED = 0; // Required zero value
|
|
207
|
+
USERROLE_ADMIN = 1;
|
|
208
|
+
USERROLE_USER = 2;
|
|
209
|
+
USERROLE_MODERATOR = 3;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
message User {
|
|
213
|
+
UserRole role = 1;
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Using with gRPC
|
|
218
|
+
|
|
219
|
+
1. Generate the .proto file:
|
|
220
|
+
```bash
|
|
221
|
+
fake_data_dsl export --all -d schemas/ -f protobuf -o service.proto
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
2. Compile to your language:
|
|
225
|
+
```bash
|
|
226
|
+
# Go
|
|
227
|
+
protoc --go_out=. --go-grpc_out=. service.proto
|
|
228
|
+
|
|
229
|
+
# Ruby
|
|
230
|
+
grpc_tools_ruby_protoc --ruby_out=. --grpc_out=. service.proto
|
|
231
|
+
|
|
232
|
+
# Python
|
|
233
|
+
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. service.proto
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
3. Implement the service using FakeDataDSL for data generation.
|
|
237
|
+
|
|
238
|
+
## Example gRPC Server (Ruby)
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
require 'grpc'
|
|
242
|
+
require_relative 'service_services_pb'
|
|
243
|
+
require 'fake_data_dsl'
|
|
244
|
+
|
|
245
|
+
class DataGenerationService < Generated::DataGenerationService::Service
|
|
246
|
+
def initialize
|
|
247
|
+
@registry = FakeDataDSL::Registry.new
|
|
248
|
+
@registry.load_dir("schemas/")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def generate_user(request, _call)
|
|
252
|
+
schema = @registry.schema("User")
|
|
253
|
+
data = schema.generate(
|
|
254
|
+
seed: request.seed.zero? ? nil : request.seed,
|
|
255
|
+
mode: request.mode.empty? ? :random : request.mode.to_sym
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
Generated::User.new(data)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def generate_many_users(request, _call)
|
|
262
|
+
schema = @registry.schema("User")
|
|
263
|
+
|
|
264
|
+
return enum_for(:generate_many_users, request, _call) unless block_given?
|
|
265
|
+
|
|
266
|
+
schema.generate_stream(count: request.count).each do |data|
|
|
267
|
+
yield Generated::User.new(data)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
server = GRPC::RpcServer.new
|
|
273
|
+
server.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
|
|
274
|
+
server.handle(DataGenerationService.new)
|
|
275
|
+
server.run_till_terminated
|
|
276
|
+
```
|