rider-kick 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +629 -25
  3. data/lib/generators/rider_kick/USAGE +2 -0
  4. data/lib/generators/rider_kick/base_generator.rb +190 -0
  5. data/lib/generators/rider_kick/clean_arch_generator.rb +235 -45
  6. data/lib/generators/rider_kick/clean_arch_generator_engine_spec.rb +359 -0
  7. data/lib/generators/rider_kick/clean_arch_generator_factory_bot_spec.rb +131 -0
  8. data/lib/generators/rider_kick/entity_type_mapping_spec.rb +22 -13
  9. data/lib/generators/rider_kick/errors.rb +42 -0
  10. data/lib/generators/rider_kick/factory_generator.rb +238 -0
  11. data/lib/generators/rider_kick/factory_generator_spec.rb +175 -0
  12. data/lib/generators/rider_kick/repositories_contract_spec.rb +95 -22
  13. data/lib/generators/rider_kick/scaffold_generator.rb +377 -62
  14. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +119 -14
  15. data/lib/generators/rider_kick/scaffold_generator_conditional_filtering_spec.rb +820 -0
  16. data/lib/generators/rider_kick/scaffold_generator_contracts_spec.rb +37 -10
  17. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +40 -11
  18. data/lib/generators/rider_kick/scaffold_generator_engine_spec.rb +221 -0
  19. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +38 -13
  20. data/lib/generators/rider_kick/scaffold_generator_list_spec_format_spec.rb +153 -0
  21. data/lib/generators/rider_kick/scaffold_generator_rspec_spec.rb +347 -0
  22. data/lib/generators/rider_kick/scaffold_generator_success_spec.rb +31 -12
  23. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +32 -11
  24. data/lib/generators/rider_kick/structure_generator.rb +154 -43
  25. data/lib/generators/rider_kick/structure_generator_comprehensive_spec.rb +598 -0
  26. data/lib/generators/rider_kick/structure_generator_engine_spec.rb +279 -0
  27. data/lib/generators/rider_kick/structure_generator_spec.rb +3 -3
  28. data/lib/generators/rider_kick/structure_generator_success_spec.rb +33 -5
  29. data/lib/generators/rider_kick/structure_generator_unit_spec.rb +2202 -0
  30. data/lib/generators/rider_kick/templates/.rubocop.yml +5 -4
  31. data/lib/generators/rider_kick/templates/config/initializers/version.rb.tt +1 -1
  32. data/lib/generators/rider_kick/templates/db/migrate/20220613145533_init_database.rb +1 -1
  33. data/lib/generators/rider_kick/templates/db/structures/example.yaml.tt +140 -66
  34. data/lib/generators/rider_kick/templates/domains/core/builders/builder.rb.tt +36 -10
  35. data/lib/generators/rider_kick/templates/domains/core/builders/builder_spec.rb.tt +219 -0
  36. data/lib/generators/rider_kick/templates/domains/core/builders/error.rb.tt +2 -2
  37. data/lib/generators/rider_kick/templates/domains/core/builders/pagination.rb.tt +2 -2
  38. data/lib/generators/rider_kick/templates/domains/core/entities/entity.rb.tt +32 -14
  39. data/lib/generators/rider_kick/templates/domains/core/entities/error.rb.tt +1 -1
  40. data/lib/generators/rider_kick/templates/domains/core/entities/pagination.rb.tt +1 -1
  41. data/lib/generators/rider_kick/templates/domains/core/repositories/abstract_repository.rb.tt +4 -4
  42. data/lib/generators/rider_kick/templates/domains/core/repositories/create.rb.tt +2 -2
  43. data/lib/generators/rider_kick/templates/domains/core/repositories/create_spec.rb.tt +78 -0
  44. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy.rb.tt +2 -2
  45. data/lib/generators/rider_kick/templates/domains/core/repositories/destroy_spec.rb.tt +88 -0
  46. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id.rb.tt +3 -3
  47. data/lib/generators/rider_kick/templates/domains/core/repositories/fetch_by_id_spec.rb.tt +62 -0
  48. data/lib/generators/rider_kick/templates/domains/core/repositories/list.rb.tt +13 -8
  49. data/lib/generators/rider_kick/templates/domains/core/repositories/list_spec.rb.tt +190 -0
  50. data/lib/generators/rider_kick/templates/domains/core/repositories/update.rb.tt +4 -4
  51. data/lib/generators/rider_kick/templates/domains/core/repositories/update_spec.rb.tt +119 -0
  52. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/default.rb.tt +1 -1
  53. data/lib/generators/rider_kick/templates/domains/core/use_cases/contract/pagination.rb.tt +1 -1
  54. data/lib/generators/rider_kick/templates/domains/core/use_cases/create.rb.tt +3 -7
  55. data/lib/generators/rider_kick/templates/domains/core/use_cases/create_spec.rb.tt +71 -0
  56. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy.rb.tt +3 -7
  57. data/lib/generators/rider_kick/templates/domains/core/use_cases/destroy_spec.rb.tt +62 -0
  58. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id.rb.tt +3 -7
  59. data/lib/generators/rider_kick/templates/domains/core/use_cases/fetch_by_id_spec.rb.tt +62 -0
  60. data/lib/generators/rider_kick/templates/domains/core/use_cases/get_version.rb.tt +2 -2
  61. data/lib/generators/rider_kick/templates/domains/core/use_cases/list.rb.tt +3 -7
  62. data/lib/generators/rider_kick/templates/domains/core/use_cases/list_spec.rb.tt +64 -0
  63. data/lib/generators/rider_kick/templates/domains/core/use_cases/update.rb.tt +3 -7
  64. data/lib/generators/rider_kick/templates/domains/core/use_cases/update_spec.rb.tt +73 -0
  65. data/lib/generators/rider_kick/templates/domains/core/utils/abstract_utils.rb.tt +3 -3
  66. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +1 -1
  67. data/lib/generators/rider_kick/templates/env.development +1 -1
  68. data/lib/generators/rider_kick/templates/env.production +1 -1
  69. data/lib/generators/rider_kick/templates/env.test +1 -1
  70. data/lib/generators/rider_kick/templates/models/{application_record.rb → application_record.rb.tt} +3 -1
  71. data/lib/generators/rider_kick/templates/models/model_spec.rb.tt +68 -0
  72. data/lib/generators/rider_kick/templates/spec/factories/.gitkeep +19 -0
  73. data/lib/generators/rider_kick/templates/spec/factories/factory.rb.tt +8 -0
  74. data/lib/generators/rider_kick/templates/spec/rails_helper.rb +2 -0
  75. data/lib/generators/rider_kick/templates/spec/support/class_stubber.rb +148 -0
  76. data/lib/generators/rider_kick/templates/spec/support/factory_bot.rb +34 -0
  77. data/lib/generators/rider_kick/templates/spec/support/faker.rb +61 -0
  78. data/lib/rider-kick.rb +8 -6
  79. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +644 -0
  80. data/lib/rider_kick/configuration.rb +238 -0
  81. data/lib/rider_kick/configuration_engine_spec.rb +377 -0
  82. data/lib/rider_kick/entities/failure_details.rb +1 -1
  83. data/lib/rider_kick/entities/failure_details_spec.rb +1 -1
  84. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  85. data/lib/rider_kick/use_cases/abstract_use_case.rb +1 -1
  86. data/lib/rider_kick/version.rb +1 -1
  87. metadata +129 -8
  88. data/CHANGELOG.md +0 -5
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'generators/rider_kick/clean_arch_generator'
7
+
8
+ RSpec.describe 'rider_kick:clean_arch generator with engine option' do
9
+ let(:klass) { RiderKick::CleanArchGenerator }
10
+
11
+ context 'without --engine option (main app)' do
12
+ it 'uses main app models path when no engine specified' do
13
+ Dir.mktmpdir do |dir|
14
+ Dir.chdir(dir) do
15
+ FileUtils.mkdir_p('config')
16
+ File.write('Gemfile', "source 'https://rubygems.org'\n")
17
+
18
+ # Reset configuration untuk test
19
+ RiderKick.configuration.engine_name = nil
20
+
21
+ instance = klass.new([])
22
+ opts = double('options', setup: true)
23
+ allow(opts).to receive(:[]).and_return(nil)
24
+ allow(opts).to receive(:[]).with(:engine).and_return(nil)
25
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
26
+ allow(instance).to receive(:options).and_return(opts)
27
+
28
+ # Hanya test setup_domain_structure yang relevan dengan engine configuration
29
+ allow(instance).to receive(:copy_domain_file) # Skip template yang memerlukan Rails
30
+ allow(instance).to receive(:setup_initializers) # Skip initializers yang memerlukan Rails
31
+ allow(instance).to receive(:setup_dotenv)
32
+ allow(instance).to receive(:setup_gitignore)
33
+ allow(instance).to receive(:setup_rubocop)
34
+ allow(instance).to receive(:setup_init_migration)
35
+ allow(instance).to receive(:setup_active_storage)
36
+ allow(instance).to receive(:setup_rspec)
37
+ allow(instance).to receive(:setup_readme)
38
+ allow(instance).to receive(:create_gem_dependencies)
39
+ allow(instance).to receive(:run)
40
+
41
+ instance.setup_configuration
42
+
43
+ # Verifikasi models_path menggunakan main app
44
+ expect(RiderKick.configuration.models_path).to eq('app/models/models')
45
+ expect(RiderKick.configuration.engine_name).to be_nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'with --engine option' do
52
+ it 'uses engine models path when engine specified' do
53
+ Dir.mktmpdir do |dir|
54
+ Dir.chdir(dir) do
55
+ FileUtils.mkdir_p('config')
56
+ File.write('Gemfile', "source 'https://rubygems.org'\n")
57
+
58
+ # Reset configuration untuk test
59
+ RiderKick.configuration.engine_name = nil
60
+
61
+ instance = klass.new([])
62
+ opts = double('options', setup: true)
63
+ allow(opts).to receive(:[]).and_return(nil)
64
+ allow(opts).to receive(:[]).with(:engine).and_return('Core')
65
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
66
+ allow(instance).to receive(:options).and_return(opts)
67
+
68
+ # Hanya test setup_domain_structure yang relevan dengan engine configuration
69
+ allow(instance).to receive(:copy_domain_file) # Skip template yang memerlukan Rails
70
+ allow(instance).to receive(:setup_initializers) # Skip initializers yang memerlukan Rails
71
+ allow(instance).to receive(:setup_dotenv)
72
+ allow(instance).to receive(:setup_gitignore)
73
+ allow(instance).to receive(:setup_rubocop)
74
+ allow(instance).to receive(:setup_init_migration)
75
+ allow(instance).to receive(:setup_active_storage)
76
+ allow(instance).to receive(:setup_rspec)
77
+ allow(instance).to receive(:setup_readme)
78
+ allow(instance).to receive(:create_gem_dependencies)
79
+ allow(instance).to receive(:run)
80
+
81
+ instance.setup_configuration
82
+
83
+ # Verifikasi models_path menggunakan engine
84
+ expect(RiderKick.configuration.models_path).to eq('engines/core/app/models/core/models')
85
+ expect(RiderKick.configuration.engine_name).to eq('Core')
86
+ expect(RiderKick.configuration.domain_scope).to eq('core/')
87
+ end
88
+ end
89
+ end
90
+
91
+ it 'creates models directory with correct engine path' do
92
+ Dir.mktmpdir do |dir|
93
+ Dir.chdir(dir) do
94
+ FileUtils.mkdir_p('config')
95
+ File.write('Gemfile', "source 'https://rubygems.org'\n")
96
+
97
+ # Reset configuration untuk test
98
+ RiderKick.configuration.engine_name = nil
99
+
100
+ instance = klass.new([])
101
+ opts = double('options', setup: true)
102
+ allow(opts).to receive(:[]).and_return(nil)
103
+ allow(opts).to receive(:[]).with(:engine).and_return('Admin')
104
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
105
+ allow(instance).to receive(:options).and_return(opts)
106
+
107
+ # Hanya test setup_domain_structure yang relevan dengan engine configuration
108
+ allow(instance).to receive(:copy_domain_file) # Skip template yang memerlukan Rails
109
+ allow(instance).to receive(:setup_initializers) # Skip initializers yang memerlukan Rails
110
+ allow(instance).to receive(:setup_dotenv)
111
+ allow(instance).to receive(:setup_gitignore)
112
+ allow(instance).to receive(:setup_rubocop)
113
+ allow(instance).to receive(:setup_init_migration)
114
+ allow(instance).to receive(:setup_active_storage)
115
+ allow(instance).to receive(:setup_rspec)
116
+ allow(instance).to receive(:setup_readme)
117
+ allow(instance).to receive(:create_gem_dependencies)
118
+ allow(instance).to receive(:run)
119
+
120
+ instance.setup_configuration
121
+
122
+ # Verifikasi directory models dibuat dengan path engine
123
+ expect(Dir.exist?('engines/admin/app/models/admin')).to be true
124
+ expect(RiderKick.configuration.models_path).to eq('engines/admin/app/models/admin/models')
125
+ expect(RiderKick.configuration.domain_scope).to eq('admin/')
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ context 'domain scope default behavior' do
132
+ it 'uses empty domain scope as default' do
133
+ Dir.mktmpdir do |dir|
134
+ Dir.chdir(dir) do
135
+ # Reset configuration to defaults
136
+ RiderKick.configuration.engine_name = nil
137
+ RiderKick.configuration.domain_scope = ''
138
+
139
+ # Mock the domain_class_name method to return predictable result
140
+ generator = klass.new([])
141
+ allow(generator).to receive(:domain_class_name).and_return('MyApp')
142
+
143
+ expect(RiderKick.configuration.domain_scope).to eq('')
144
+ expect(RiderKick.configuration.domains_path).to eq('app/domains/')
145
+ end
146
+ end
147
+ end
148
+
149
+ it 'generates class names with app name for empty domain scope in main app' do
150
+ Dir.mktmpdir do |dir|
151
+ Dir.chdir(dir) do
152
+ # Create minimal Rails app structure for testing
153
+ FileUtils.mkdir_p('app/domains')
154
+
155
+ # Reset configuration
156
+ RiderKick.configuration.engine_name = nil
157
+ RiderKick.configuration.domain_scope = ''
158
+
159
+ instance = klass.new([])
160
+ opts = double('options', setup: true)
161
+ allow(opts).to receive(:[]).and_return(nil)
162
+ allow(opts).to receive(:[]).with(:engine).and_return(nil)
163
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
164
+ allow(instance).to receive(:options).and_return(opts)
165
+
166
+ # Mock methods to avoid actual file generation
167
+ allow(instance).to receive(:setup_domain_structure)
168
+ allow(instance).to receive(:setup_initializers)
169
+ allow(instance).to receive(:setup_dotenv)
170
+ allow(instance).to receive(:setup_gitignore)
171
+ allow(instance).to receive(:setup_rubocop)
172
+ allow(instance).to receive(:setup_active_storage)
173
+ allow(instance).to receive(:setup_rspec)
174
+ allow(instance).to receive(:setup_readme)
175
+ allow(instance).to receive(:create_gem_dependencies)
176
+ allow(instance).to receive(:setup_init_migration)
177
+ allow(instance).to receive(:setup_models)
178
+ allow(instance).to receive(:say)
179
+
180
+ # Test domain_class_name method
181
+ expect(instance.send(:domain_class_name)).to eq('MyApp')
182
+
183
+ # Test configuration paths
184
+ expect(RiderKick.configuration.domains_path).to eq('app/domains/')
185
+ expect(RiderKick.configuration.models_path).to eq('app/models/models')
186
+ end
187
+ end
188
+ end
189
+
190
+ it 'generates class names with engine name for engine with default domain scope' do
191
+ Dir.mktmpdir do |dir|
192
+ Dir.chdir(dir) do
193
+ # Create minimal engine structure for testing
194
+ FileUtils.mkdir_p('engines/admin/lib/admin/engine.rb')
195
+
196
+ # Reset configuration
197
+ RiderKick.configuration.engine_name = 'Admin'
198
+ RiderKick.configuration.domain_scope = 'admin/' # Engine-prefixed domain scope
199
+
200
+ instance = klass.new([])
201
+ opts = double('options', setup: true)
202
+ allow(opts).to receive(:[]).and_return(nil)
203
+ allow(opts).to receive(:[]).with(:engine).and_return('Admin')
204
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
205
+ allow(instance).to receive(:options).and_return(opts)
206
+
207
+ # Mock methods to avoid actual file generation
208
+ allow(instance).to receive(:setup_domain_structure)
209
+ allow(instance).to receive(:setup_initializers)
210
+ allow(instance).to receive(:setup_dotenv)
211
+ allow(instance).to receive(:setup_gitignore)
212
+ allow(instance).to receive(:setup_rubocop)
213
+ allow(instance).to receive(:setup_active_storage)
214
+ allow(instance).to receive(:setup_rspec)
215
+ allow(instance).to receive(:setup_readme)
216
+ allow(instance).to receive(:create_gem_dependencies)
217
+ allow(instance).to receive(:setup_init_migration)
218
+ allow(instance).to receive(:setup_models)
219
+ allow(instance).to receive(:say)
220
+
221
+ # Test domain_class_name method for engine with empty domain scope
222
+ expect(instance.send(:domain_class_name)).to eq('Admin')
223
+
224
+ # Test configuration paths
225
+ expect(RiderKick.configuration.domains_path).to eq('engines/admin/app/domains/admin/')
226
+ expect(RiderKick.configuration.models_path).to eq('engines/admin/app/models/admin/models')
227
+ end
228
+ end
229
+ end
230
+
231
+ it 'generates class names with engine and domain for scoped domain in engine' do
232
+ Dir.mktmpdir do |dir|
233
+ Dir.chdir(dir) do
234
+ # Create minimal engine structure for testing
235
+ FileUtils.mkdir_p('engines/admin/lib/admin/engine.rb')
236
+
237
+ # Reset configuration
238
+ RiderKick.configuration.engine_name = 'Admin'
239
+ RiderKick.configuration.domain_scope = 'admin/core/' # Engine-prefixed
240
+
241
+ instance = klass.new([])
242
+ opts = double('options', setup: true)
243
+ allow(opts).to receive(:[]).and_return(nil)
244
+ allow(opts).to receive(:[]).with(:engine).and_return('Admin')
245
+ allow(opts).to receive(:[]).with(:domain).and_return('core/')
246
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
247
+ allow(instance).to receive(:options).and_return(opts)
248
+
249
+ # Mock methods to avoid actual file generation
250
+ allow(instance).to receive(:setup_domain_structure)
251
+ allow(instance).to receive(:setup_initializers)
252
+ allow(instance).to receive(:setup_dotenv)
253
+ allow(instance).to receive(:setup_gitignore)
254
+ allow(instance).to receive(:setup_rubocop)
255
+ allow(instance).to receive(:setup_active_storage)
256
+ allow(instance).to receive(:setup_rspec)
257
+ allow(instance).to receive(:setup_readme)
258
+ allow(instance).to receive(:create_gem_dependencies)
259
+ allow(instance).to receive(:setup_init_migration)
260
+ allow(instance).to receive(:setup_models)
261
+ allow(instance).to receive(:say)
262
+
263
+ # Test domain_class_name method for engine with domain
264
+ expect(instance.send(:domain_class_name)).to eq('Admin::Core')
265
+
266
+ # Test domain_class_name_for_application_record method
267
+ expect(instance.send(:domain_class_name_for_application_record)).to eq('Admin')
268
+
269
+ # Test configuration paths - when both engine and domain specified, structure goes to engine domains
270
+ expect(RiderKick.configuration.domains_path).to eq('engines/admin/app/domains/admin/core/')
271
+ expect(RiderKick.configuration.models_path).to eq('engines/admin/app/models/admin/models')
272
+ end
273
+ end
274
+ end
275
+
276
+ it 'creates domains structure in engine when both engine and domain are specified during setup' do
277
+ Dir.mktmpdir do |dir|
278
+ Dir.chdir(dir) do
279
+ # Create minimal Rails app structure for testing
280
+ FileUtils.mkdir_p('app/domains')
281
+
282
+ # Reset configuration
283
+ RiderKick.configuration.engine_name = 'MyEngine'
284
+ RiderKick.configuration.domain_scope = 'admin/'
285
+
286
+ instance = klass.new([])
287
+ opts = double('options', setup: true)
288
+ allow(opts).to receive(:[]).and_return(nil)
289
+ allow(opts).to receive(:[]).with(:engine).and_return('MyEngine')
290
+ allow(opts).to receive(:[]).with(:domain).and_return('admin/')
291
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
292
+ allow(instance).to receive(:options).and_return(opts)
293
+
294
+ # Mock methods to avoid actual file generation
295
+ allow(instance).to receive(:setup_domain_structure)
296
+ allow(instance).to receive(:setup_initializers)
297
+ allow(instance).to receive(:setup_dotenv)
298
+ allow(instance).to receive(:setup_gitignore)
299
+ allow(instance).to receive(:setup_rubocop)
300
+ allow(instance).to receive(:setup_active_storage)
301
+ allow(instance).to receive(:setup_rspec)
302
+ allow(instance).to receive(:setup_readme)
303
+ allow(instance).to receive(:create_gem_dependencies)
304
+ allow(instance).to receive(:setup_init_migration)
305
+ allow(instance).to receive(:setup_models)
306
+ allow(instance).to receive(:say)
307
+
308
+ # Test domain_class_name method for engine with domain
309
+ expect(instance.send(:domain_class_name)).to eq('MyEngine::Admin')
310
+
311
+ # Test configuration paths - should create structure in engine domains
312
+ expect(RiderKick.configuration.domains_path).to eq('engines/my_engine/app/domains/admin/')
313
+ expect(RiderKick.configuration.models_path).to eq('engines/my_engine/app/models/my_engine/models')
314
+ end
315
+ end
316
+ end
317
+
318
+ it 'generates ApplicationRecord class name for main app' do
319
+ Dir.mktmpdir do |dir|
320
+ Dir.chdir(dir) do
321
+ # Create minimal Rails app structure for testing
322
+ FileUtils.mkdir_p('app/domains')
323
+
324
+ # Reset configuration
325
+ RiderKick.configuration.engine_name = nil
326
+ RiderKick.configuration.domain_scope = ''
327
+
328
+ instance = klass.new([])
329
+ opts = double('options', setup: true)
330
+ allow(opts).to receive(:[]).and_return(nil)
331
+ allow(opts).to receive(:[]).with(:engine).and_return(nil)
332
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
333
+ allow(instance).to receive(:options).and_return(opts)
334
+
335
+ # Mock methods to avoid actual file generation
336
+ allow(instance).to receive(:setup_domain_structure)
337
+ allow(instance).to receive(:setup_initializers)
338
+ allow(instance).to receive(:setup_dotenv)
339
+ allow(instance).to receive(:setup_gitignore)
340
+ allow(instance).to receive(:setup_rubocop)
341
+ allow(instance).to receive(:setup_active_storage)
342
+ allow(instance).to receive(:setup_rspec)
343
+ allow(instance).to receive(:setup_readme)
344
+ allow(instance).to receive(:create_gem_dependencies)
345
+ allow(instance).to receive(:setup_init_migration)
346
+ allow(instance).to receive(:setup_models)
347
+ allow(instance).to receive(:say)
348
+
349
+ # Test domain_class_name_for_application_record method for main app
350
+ expect(instance.send(:domain_class_name_for_application_record)).to eq('')
351
+
352
+ # Test configuration paths
353
+ expect(RiderKick.configuration.domains_path).to eq('app/domains/')
354
+ expect(RiderKick.configuration.models_path).to eq('app/models/models')
355
+ end
356
+ end
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'generators/rider_kick/clean_arch_generator'
7
+
8
+ RSpec.describe 'rider_kick:clean_arch generator (FactoryBot & Faker setup)' do
9
+ let(:klass) { RiderKick::CleanArchGenerator }
10
+
11
+ it 'sets up FactoryBot and Faker with proper directory structure and configuration' do
12
+ Dir.mktmpdir do |dir|
13
+ Dir.chdir(dir) do
14
+ # Setup minimal Rails structure
15
+ FileUtils.mkdir_p('config')
16
+ FileUtils.mkdir_p('spec')
17
+ File.write('Gemfile', "source 'https://rubygems.org'\n")
18
+
19
+ instance = klass.new([])
20
+ opts = double('options', setup: true)
21
+ allow(opts).to receive(:[]).and_return(nil)
22
+ allow(opts).to receive(:[]).with(:setup).and_return(true)
23
+ allow(opts).to receive(:[]).with(:engine).and_return(nil)
24
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
25
+ allow(instance).to receive(:options).and_return(opts)
26
+
27
+ # Skip other setup methods
28
+ allow(instance).to receive(:copy_domain_file)
29
+ allow(instance).to receive(:setup_initializers)
30
+ allow(instance).to receive(:setup_dotenv)
31
+ allow(instance).to receive(:setup_gitignore)
32
+ allow(instance).to receive(:setup_rubocop)
33
+ allow(instance).to receive(:setup_init_migration)
34
+ allow(instance).to receive(:setup_models)
35
+ allow(instance).to receive(:setup_active_storage)
36
+ allow(instance).to receive(:setup_readme)
37
+ allow(instance).to receive(:run)
38
+
39
+ # Call setup methods
40
+ instance.send(:setup_domain_structure)
41
+ instance.send(:setup_rspec)
42
+
43
+ # Verify FactoryBot directory structure
44
+ expect(Dir.exist?('spec/factories')).to be true
45
+
46
+ # Verify FactoryBot support file
47
+ factory_bot_support = 'spec/support/factory_bot.rb'
48
+ expect(File.exist?(factory_bot_support)).to be true
49
+
50
+ factory_bot_content = File.read(factory_bot_support)
51
+ expect(factory_bot_content).to include('FactoryBot::Syntax::Methods')
52
+ expect(factory_bot_content).to include('config.include FactoryBot::Syntax::Methods')
53
+ expect(factory_bot_content).to include('sequence :email')
54
+ expect(factory_bot_content).to include('sequence :uuid')
55
+
56
+ # Verify Faker support file
57
+ faker_support = 'spec/support/faker.rb'
58
+ expect(File.exist?(faker_support)).to be true
59
+
60
+ faker_content = File.read(faker_support)
61
+ expect(faker_content).to include('Faker::Config')
62
+ expect(faker_content).to include('module FakerHelpers')
63
+ expect(faker_content).to include('def self.random_phone')
64
+ expect(faker_content).to include('def self.random_address')
65
+ expect(faker_content).to include('def self.random_url')
66
+ expect(faker_content).to include('config.include FakerHelpers')
67
+
68
+ # Verify .gitkeep file
69
+ gitkeep = 'spec/factories/.gitkeep'
70
+ expect(File.exist?(gitkeep)).to be true
71
+
72
+ gitkeep_content = File.read(gitkeep)
73
+ expect(gitkeep_content).to include('FactoryBot.define')
74
+ expect(gitkeep_content).to include('Example:')
75
+
76
+ # Verify rails_helper includes
77
+ rails_helper = 'spec/rails_helper.rb'
78
+ expect(File.exist?(rails_helper)).to be true
79
+
80
+ rails_helper_content = File.read(rails_helper)
81
+ expect(rails_helper_content).to include("Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }")
82
+ expect(rails_helper_content).to include('config.include FactoryBot::Syntax::Methods')
83
+ expect(rails_helper_content).to include('config.include FakerHelpers')
84
+ end
85
+ end
86
+ end
87
+
88
+ it 'generates Gemfile with correct dependencies' do
89
+ Dir.mktmpdir do |dir|
90
+ Dir.chdir(dir) do
91
+ FileUtils.mkdir_p('config')
92
+ File.write('Gemfile', <<~GEMFILE)
93
+ source 'https://rubygems.org'
94
+
95
+ group :development, :test do
96
+ end
97
+ GEMFILE
98
+
99
+ instance = klass.new([])
100
+ opts = double('options', setup: true)
101
+ allow(opts).to receive(:[]).and_return(nil)
102
+ allow(opts).to receive(:[]).with(:setup).and_return(true)
103
+ allow(opts).to receive(:[]).with(:engine).and_return(nil)
104
+ allow(opts).to receive(:[]).with(:quiet).and_return(false)
105
+ allow(instance).to receive(:options).and_return(opts)
106
+
107
+ # Skip validation and other setup methods
108
+ allow(instance).to receive(:validate_setup_option)
109
+ allow(instance).to receive(:setup_configuration)
110
+ allow(instance).to receive(:run)
111
+
112
+ # Only run create_gem_dependencies
113
+ instance.create_gem_dependencies
114
+
115
+ gemfile_content = File.read('Gemfile')
116
+
117
+ # Verify gems are added in correct group
118
+ expect(gemfile_content).to include('group :development, :test do')
119
+ expect(gemfile_content).to include('gem "rspec-rails"')
120
+ expect(gemfile_content).to include('gem "factory_bot_rails"')
121
+ expect(gemfile_content).to include('gem "faker"')
122
+ expect(gemfile_content).to include('gem "shoulda-matchers"')
123
+
124
+ # Verify other gems
125
+ expect(gemfile_content).to include("gem 'dotenv-rails'")
126
+ expect(gemfile_content).to include("gem 'hashie'")
127
+ expect(gemfile_content).to include("gem 'pagy'")
128
+ end
129
+ end
130
+ end
131
+ end
@@ -4,22 +4,26 @@
4
4
  require 'rails/generators'
5
5
  require 'tmpdir'
6
6
  require 'fileutils'
7
- require 'ostruct'
8
7
  require 'generators/rider_kick/scaffold_generator'
9
8
 
10
9
  RSpec.describe 'entity type mapping' do
11
10
  let(:klass) { RiderKick::ScaffoldGenerator }
12
11
 
13
12
  it 'memetakan kolom ke Types::Strict yang benar' do
13
+ # Store original configuration
14
+ original_engine = RiderKick.configuration.engine_name
15
+ original_domain = RiderKick.configuration.domain_scope
16
+
14
17
  Dir.mktmpdir do |dir|
15
18
  Dir.chdir(dir) do
16
- FileUtils.mkdir_p %w[
17
- app/domains/core/use_cases
18
- app/domains/core/repositories
19
- app/domains/core/builders
20
- app/domains/core/entities
21
- app/models/models
22
- db/structures
19
+ # Reset configuration for this test
20
+ RiderKick.configuration.engine_name = nil
21
+ RiderKick.configuration.domain_scope = 'core/'
22
+
23
+ FileUtils.mkdir_p [
24
+ RiderKick.configuration.domains_path,
25
+ 'app/models/models',
26
+ 'db/structures'
23
27
  ]
24
28
 
25
29
  # pakai stub shared Models::User dari spec/support (sudah auto-require)
@@ -28,7 +32,9 @@ RSpec.describe 'entity type mapping' do
28
32
  model: Models::User
29
33
  resource_name: users
30
34
  actor: owner
31
- uploaders: []
35
+ resource_owner_id: account_id
36
+ resource_owner: account
37
+ uploaders: [{ name: 'avatar', type: 'single' }]
32
38
  search_able: []
33
39
  domains:
34
40
  action_list: { use_case: { contract: [] } }
@@ -36,17 +42,20 @@ RSpec.describe 'entity type mapping' do
36
42
  action_create: { use_case: { contract: [] } }
37
43
  action_update: { use_case: { contract: [] } }
38
44
  action_destroy: { use_case: { contract: [] } }
39
- entity: { skipped_fields: [id, created_at, updated_at] }
45
+ entity: { db_attributes: ['id', 'name', 'price', 'created_at', 'updated_at'] }
40
46
  YAML
41
47
 
42
48
  klass.new(['users']).generate_use_case
43
49
 
44
- entity = File.read('app/domains/core/entities/user.rb')
45
- # harapannya: string, decimal, datetime dipetakan ke Strict
50
+ entity = File.read(RiderKick.configuration.domains_path + '/entities/user.rb')
51
+ # harapannya: string, datetime dipetakan ke Strict
46
52
  expect(entity).to match(/Types::Strict::String/)
47
- expect(entity).to match(/Types::Strict::Decimal|Types::Coercible::Decimal/)
48
53
  expect(entity).to match(/Types::Strict::Time|Types::Strict::Date|Types::Strict::DateTime/)
49
54
  end
55
+ ensure
56
+ # Restore original configuration
57
+ RiderKick.configuration.engine_name = original_engine
58
+ RiderKick.configuration.domain_scope = original_domain
50
59
  end
51
60
  end
52
61
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RiderKick
4
+ # Base error class for all generator errors
5
+ class GeneratorError < StandardError
6
+ attr_reader :context
7
+
8
+ def initialize(message, context = {})
9
+ super(message)
10
+ @context = context
11
+ end
12
+
13
+ def to_s
14
+ if @context.any?
15
+ context_str = @context.map { |k, v| "#{k}: #{v}" }.join(', ')
16
+ "#{super} (#{context_str})"
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+
23
+ # Error raised when validation fails
24
+ class ValidationError < GeneratorError
25
+ end
26
+
27
+ # Error raised when configuration is invalid
28
+ class ConfigurationError < GeneratorError
29
+ end
30
+
31
+ # Error raised when a model is not found
32
+ class ModelNotFoundError < GeneratorError
33
+ end
34
+
35
+ # Error raised when a file is not found
36
+ class FileNotFoundError < GeneratorError
37
+ end
38
+
39
+ # Error raised when YAML format is invalid
40
+ class YamlFormatError < GeneratorError
41
+ end
42
+ end