rider-kick 0.0.12 → 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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +668 -27
  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 -44
  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 +61 -0
  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 +135 -0
  13. data/lib/generators/rider_kick/scaffold_generator.rb +377 -62
  14. data/lib/generators/rider_kick/scaffold_generator_builder_uploaders_spec.rb +159 -0
  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 +96 -0
  17. data/lib/generators/rider_kick/scaffold_generator_contracts_with_scope_spec.rb +83 -0
  18. data/lib/generators/rider_kick/scaffold_generator_engine_spec.rb +221 -0
  19. data/lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb +84 -0
  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 +101 -0
  23. data/lib/generators/rider_kick/scaffold_generator_with_scope_spec.rb +76 -0
  24. data/lib/generators/rider_kick/structure_generator.rb +179 -35
  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 +20 -0
  28. data/lib/generators/rider_kick/structure_generator_success_spec.rb +64 -0
  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 +157 -51
  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 -16
  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 +12 -7
  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 +29 -0
  66. data/lib/generators/rider_kick/templates/domains/core/utils/request_methods.rb.tt +3 -2
  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 +9 -6
  79. data/lib/rider_kick/builders/abstract_active_record_entity_builder_spec.rb +596 -68
  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 +23 -15
  83. data/lib/rider_kick/entities/failure_details_spec.rb +22 -0
  84. data/lib/rider_kick/matchers/use_case_result.rb +1 -1
  85. data/lib/rider_kick/matchers/use_case_result_edge_spec.rb +28 -0
  86. data/lib/rider_kick/use_cases/abstract_use_case.rb +1 -1
  87. data/lib/rider_kick/use_cases/abstract_use_case_spec.rb +57 -0
  88. data/lib/rider_kick/version.rb +1 -1
  89. metadata +345 -52
  90. data/.rspec +0 -3
  91. data/.rubocop.yml +0 -1141
  92. data/CHANGELOG.md +0 -5
  93. data/Rakefile +0 -12
  94. data/lib/rider_kick/matchers/use_case_result_spec.rb +0 -64
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold contracts' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menulis kontrak schema yang tepat di use_cases (list/fetch_by_id/create/update/destroy)' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ # Set domain scope to core for this test
17
+ RiderKick.configuration.domain_scope = 'core/'
18
+
19
+ FileUtils.mkdir_p([
20
+ RiderKick.configuration.domains_path + '/core/use_cases',
21
+ RiderKick.configuration.domains_path + '/core/repositories',
22
+ RiderKick.configuration.domains_path + '/core/builders',
23
+ RiderKick.configuration.domains_path + '/core/entities',
24
+ 'app/models/models',
25
+ 'db/structures'
26
+ ])
27
+
28
+ # Stub model & kolom
29
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
30
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
31
+ module Models; end
32
+
33
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
34
+ class Models::User
35
+ def self.columns
36
+ [
37
+ Column.new('id', :uuid),
38
+ Column.new('created_at', :datetime),
39
+ Column.new('updated_at', :datetime)
40
+ ]
41
+ end
42
+
43
+ def self.columns_hash
44
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
45
+ end
46
+
47
+ def self.column_names
48
+ columns.map { |c| c.name.to_s }
49
+ end
50
+ end
51
+
52
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
53
+
54
+ # YAML struktur minimal
55
+ File.write('db/structures/users_structure.yaml', <<~YAML)
56
+ model: Models::User
57
+ resource_name: users
58
+ actor: owner
59
+ resource_owner_id: account_id
60
+ resource_owner: account
61
+ uploaders: []
62
+ search_able: []
63
+ domains:
64
+ action_list: { use_case: { contract: [] } }
65
+ action_fetch_by_id: { use_case: { contract: [] } }
66
+ action_create: { use_case: { contract: [] } }
67
+ action_update: { use_case: { contract: [] } }
68
+ action_destroy: { use_case: { contract: [] } }
69
+ entity: { db_attributes: [id, created_at, updated_at] }
70
+ YAML
71
+
72
+ # Generate
73
+ klass.new(['users']).generate_use_case
74
+
75
+ # Cek isi kontrak
76
+ readf = ->(path) { File.read(File.join(path)) }
77
+ base = RiderKick.configuration.domains_path + '/use_cases/users'
78
+
79
+ expect(readf["#{base}/owner_list_user.rb"])
80
+ .to include('Core::UseCases::Contract::Default', 'Contract::Pagination')
81
+
82
+ expect(readf["#{base}/owner_fetch_user_by_id.rb"])
83
+ .to include('required(:id).filled(:string)')
84
+
85
+ expect(readf["#{base}/owner_create_user.rb"])
86
+ .to include('Core::Repositories::Users::CreateUser')
87
+
88
+ expect(readf["#{base}/owner_update_user.rb"])
89
+ .to include('required(:id).filled(:string)')
90
+
91
+ expect(readf["#{base}/owner_destroy_user.rb"])
92
+ .to include('required(:id).filled(:string)')
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'active_support/inflector'
6
+
7
+ require 'fileutils'
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold contracts (with scope)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'menaruh use_cases di folder scope yang benar & menulis kontrak sesuai' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ # Set domain scope to core for this test
17
+ RiderKick.configuration.domain_scope = 'core/'
18
+
19
+ FileUtils.mkdir_p([
20
+ RiderKick.configuration.domains_path + '/core/use_cases',
21
+ RiderKick.configuration.domains_path + '/core/repositories',
22
+ RiderKick.configuration.domains_path + '/core/builders',
23
+ RiderKick.configuration.domains_path + '/core/entities',
24
+ 'app/models/models',
25
+ 'db/structures'
26
+ ])
27
+
28
+ # Stub model & kolom
29
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
30
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
31
+ module Models; end
32
+
33
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
34
+ class Models::User
35
+ def self.columns
36
+ [
37
+ Column.new('id', :uuid),
38
+ Column.new('created_at', :datetime),
39
+ Column.new('updated_at', :datetime)
40
+ ]
41
+ end
42
+
43
+ def self.columns_hash
44
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
45
+ end
46
+
47
+ def self.column_names
48
+ columns.map { |c| c.name.to_s }
49
+ end
50
+ end
51
+
52
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
53
+
54
+ File.write('db/structures/users_structure.yaml', <<~YAML)
55
+ model: Models::User
56
+ resource_name: users
57
+ actor: owner
58
+ resource_owner_id: account_id
59
+ resource_owner: account
60
+ uploaders: []
61
+ search_able: []
62
+ domains:
63
+ action_list: { use_case: { contract: [] } }
64
+ action_fetch_by_id: { use_case: { contract: [] } }
65
+ action_create: { use_case: { contract: [] } }
66
+ action_update: { use_case: { contract: [] } }
67
+ action_destroy: { use_case: { contract: [] } }
68
+ entity: { db_attributes: [id, created_at, updated_at] }
69
+ YAML
70
+
71
+ klass.new(['users', 'scope:dashboard']).generate_use_case
72
+
73
+ base = RiderKick.configuration.domains_path + '/use_cases/dashboard/users'
74
+ ['owner_list_user', 'owner_fetch_user_by_id', 'owner_create_user', 'owner_update_user', 'owner_destroy_user'].each do |uc|
75
+ expect(File).to exist("#{base}/#{uc}.rb")
76
+ end
77
+
78
+ expect(File.read("#{base}/owner_list_user.rb"))
79
+ .to include('Core::UseCases::Dashboard::Users::OwnerListUser')
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'generators/rider_kick/scaffold_generator'
7
+
8
+ RSpec.describe 'rider_kick:scaffold generator with engine option' do
9
+ let(:klass) { RiderKick::ScaffoldGenerator }
10
+
11
+ context 'without --engine option (main app)' do
12
+ it 'uses main app models path (app/models/models)' do
13
+ Dir.mktmpdir do |dir|
14
+ Dir.chdir(dir) do
15
+ FileUtils.mkdir_p([
16
+ RiderKick.configuration.domains_path + '/core/use_cases',
17
+ RiderKick.configuration.domains_path + '/core/repositories',
18
+ RiderKick.configuration.domains_path + '/core/builders',
19
+ RiderKick.configuration.domains_path + '/core/entities',
20
+ 'app/models/models',
21
+ 'db/structures'
22
+ ])
23
+
24
+ # Stub model & kolom
25
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
26
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
27
+ module Models; end
28
+
29
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
30
+ class Models::User
31
+ def self.columns
32
+ [
33
+ Column.new('id', :uuid),
34
+ Column.new('created_at', :datetime),
35
+ Column.new('updated_at', :datetime)
36
+ ]
37
+ end
38
+
39
+ def self.columns_hash
40
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
41
+ end
42
+
43
+ def self.column_names
44
+ columns.map { |c| c.name.to_s }
45
+ end
46
+ end
47
+
48
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
49
+ File.write('db/structures/users_structure.yaml', <<~YAML)
50
+ model: Models::User
51
+ resource_name: users
52
+ actor: owner
53
+ resource_owner_id: account_id
54
+ resource_owner: account
55
+ uploaders: []
56
+ search_able: []
57
+ domains:
58
+ action_list: { use_case: { contract: [] } }
59
+ action_fetch_by_id: { use_case: { contract: [] } }
60
+ action_create: { use_case: { contract: [] } }
61
+ action_update: { use_case: { contract: [] } }
62
+ action_destroy: { use_case: { contract: [] } }
63
+ entity: { db_attributes: [id, created_at, updated_at] }
64
+ YAML
65
+
66
+ # Reset configuration untuk test
67
+ RiderKick.configuration.engine_name = nil
68
+
69
+ instance = klass.new(['users'])
70
+ allow(instance).to receive(:options).and_return({ engine: nil })
71
+ instance.generate_use_case
72
+
73
+ # Verifikasi models_path menggunakan main app
74
+ expect(RiderKick.configuration.models_path).to eq('app/models/models')
75
+ expect(RiderKick.configuration.engine_name).to be_nil
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ context 'with --engine option' do
82
+ it 'uses engine models path (engines/<engine_name>/app/models/<engine_name>/models)' do
83
+ Dir.mktmpdir do |dir|
84
+ Dir.chdir(dir) do
85
+ # Set configuration for test first
86
+ RiderKick.configuration.engine_name = 'Core'
87
+ RiderKick.configuration.domain_scope = 'core/'
88
+
89
+ FileUtils.mkdir_p([
90
+ RiderKick.configuration.domains_path,
91
+ RiderKick.configuration.models_path,
92
+ 'engines/core/app/models/core',
93
+ 'engines/core/db/structures'
94
+ ])
95
+
96
+ # Stub model classes
97
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
98
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
99
+ module Models; end
100
+
101
+ module Models::Core; end
102
+
103
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
104
+ class Models::Core::User
105
+ def self.columns
106
+ [
107
+ Column.new('id', :uuid),
108
+ Column.new('created_at', :datetime),
109
+ Column.new('updated_at', :datetime)
110
+ ]
111
+ end
112
+
113
+ def self.columns_hash
114
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
115
+ end
116
+
117
+ def self.column_names
118
+ columns.map { |c| c.name.to_s }
119
+ end
120
+ end
121
+
122
+ File.write('engines/core/app/models/core/models/user.rb', "class Models::Core::User < ApplicationRecord; end\n")
123
+ File.write('engines/core/db/structures/users_structure.yaml', <<~YAML)
124
+ model: Models::Core::User
125
+ resource_name: users
126
+ actor: owner
127
+ resource_owner_id: account_id
128
+ resource_owner: account
129
+ uploaders: []
130
+ search_able: []
131
+ domains:
132
+ action_list: { use_case: { contract: [] } }
133
+ action_fetch_by_id: { use_case: { contract: [] } }
134
+ action_create: { use_case: { contract: [] } }
135
+ action_update: { use_case: { contract: [] } }
136
+ action_destroy: { use_case: { contract: [] } }
137
+ entity: { db_attributes: [id, created_at, updated_at] }
138
+ YAML
139
+
140
+ instance = klass.new(['users'])
141
+ instance.generate_use_case
142
+
143
+ # Verifikasi models_path menggunakan engine
144
+ expect(RiderKick.configuration.models_path).to eq('engines/core/app/models/core/models')
145
+ expect(RiderKick.configuration.engine_name).to eq('Core')
146
+ end
147
+ end
148
+ end
149
+
150
+ it 'generates files with correct engine configuration' do
151
+ Dir.mktmpdir do |dir|
152
+ Dir.chdir(dir) do
153
+ # Set configuration for test first
154
+ RiderKick.configuration.engine_name = 'Admin'
155
+ RiderKick.configuration.domain_scope = 'admin/core/' # Engine-prefixed
156
+
157
+ FileUtils.mkdir_p([
158
+ RiderKick.configuration.domains_path,
159
+ 'app/models/admin',
160
+ 'engines/admin/db/structures'
161
+ ])
162
+
163
+ # Stub model classes
164
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
165
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
166
+ module Models; end
167
+
168
+ module Models::Admin; end
169
+
170
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
171
+ class Models::Admin::Product
172
+ def self.columns
173
+ [
174
+ Column.new('id', :uuid),
175
+ Column.new('created_at', :datetime),
176
+ Column.new('updated_at', :datetime)
177
+ ]
178
+ end
179
+
180
+ def self.columns_hash
181
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
182
+ end
183
+
184
+ def self.column_names
185
+ columns.map { |c| c.name.to_s }
186
+ end
187
+ end
188
+
189
+ File.write('app/models/admin/product.rb', "class Models::Admin::Product < ApplicationRecord; end\n")
190
+ File.write('engines/admin/db/structures/products_structure.yaml', <<~YAML)
191
+ model: Models::Admin::Product
192
+ resource_name: products
193
+ actor: admin
194
+ resource_owner_id: account_id
195
+ resource_owner: account
196
+ uploaders: []
197
+ search_able: []
198
+ domains:
199
+ action_list: { use_case: { contract: [] } }
200
+ action_fetch_by_id: { use_case: { contract: [] } }
201
+ action_create: { use_case: { contract: [] } }
202
+ action_update: { use_case: { contract: [] } }
203
+ action_destroy: { use_case: { contract: [] } }
204
+ entity: { db_attributes: [id, created_at, updated_at] }
205
+ YAML
206
+
207
+ instance = klass.new(['products'])
208
+ instance.generate_use_case
209
+
210
+ # Verifikasi engine name sudah di-set
211
+ expect(RiderKick.configuration.engine_name).to eq('Admin')
212
+ expect(RiderKick.configuration.models_path).to eq('engines/admin/app/models/admin/models')
213
+
214
+ # Verifikasi files ter-generate
215
+ expect(File).to exist(RiderKick.configuration.domains_path + '/builders/product.rb')
216
+ expect(File).to exist(RiderKick.configuration.domains_path + '/entities/product.rb')
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,84 @@
1
+ # lib/generators/rider_kick/scaffold_generator_idempotent_spec.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails/generators'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+
8
+ require 'generators/rider_kick/scaffold_generator'
9
+
10
+ RSpec.describe 'rider_kick:scaffold generator (idempotent)' do
11
+ let(:klass) { RiderKick::ScaffoldGenerator }
12
+
13
+ it 'tidak menduplikasi konten ketika dijalankan ulang' do
14
+ Dir.mktmpdir do |dir|
15
+ Dir.chdir(dir) do
16
+ # Reset configuration for this test
17
+ RiderKick.configuration.engine_name = nil
18
+ RiderKick.configuration.domain_scope = 'core/'
19
+
20
+ FileUtils.mkdir_p [
21
+ RiderKick.configuration.domains_path,
22
+ 'app/models/models',
23
+ 'db/structures'
24
+ ]
25
+
26
+ # Stub model classes
27
+ Object.send(:remove_const, :Models) if Object.const_defined?(:Models)
28
+ Object.send(:remove_const, :Column) if Object.const_defined?(:Column)
29
+ module Models; end
30
+
31
+ Column = Struct.new(:name, :type, :sql_type, :null, :default, :precision, :scale, :limit)
32
+ class Models::User
33
+ def self.columns
34
+ [
35
+ Column.new('id', :uuid),
36
+ Column.new('created_at', :datetime),
37
+ Column.new('updated_at', :datetime)
38
+ ]
39
+ end
40
+
41
+ def self.columns_hash
42
+ columns.to_h { |c| [c.name.to_s, Struct.new(:type).new(c.type)] }
43
+ end
44
+
45
+ def self.column_names
46
+ columns.map { |c| c.name.to_s }
47
+ end
48
+ end
49
+
50
+ # model fisik & YAML minimal
51
+ File.write('app/models/models/user.rb', "class Models::User < ApplicationRecord; end\n")
52
+ File.write('db/structures/users_structure.yaml', <<~YAML)
53
+ model: Models::User
54
+ resource_name: users
55
+ actor: owner
56
+ uploaders: []
57
+ search_able: []
58
+ domains:
59
+ action_list: { use_case: { contract: [] } }
60
+ action_fetch_by_id: { use_case: { contract: [] } }
61
+ action_create: { use_case: { contract: [] } }
62
+ action_update: { use_case: { contract: [] } }
63
+ action_destroy: { use_case: { contract: [] } }
64
+ entity: { db_attributes: [id, created_at, updated_at] }
65
+ YAML
66
+
67
+ # run pertama
68
+ klass.new(['users']).generate_use_case
69
+ builder_v1 = File.read(RiderKick.configuration.domains_path + '/builders/user.rb')
70
+
71
+ # run kedua (seharusnya idempotent)
72
+ klass.new(['users']).generate_use_case
73
+ builder_v2 = File.read(RiderKick.configuration.domains_path + '/builders/user.rb')
74
+
75
+ # konten tidak berubah
76
+ expect(builder_v2).to eq(builder_v1)
77
+
78
+ # entity & repositori tetap 1 file masing-masing
79
+ expect(Dir[RiderKick.configuration.domains_path + '/entities/user.rb'].size).to eq(1)
80
+ expect(Dir[RiderKick.configuration.domains_path + '/repositories/users/*.rb'].size).to be >= 5
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'tmpdir'
5
+ require 'generators/rider_kick/scaffold_generator'
6
+
7
+ RSpec.describe 'rider_kick:scaffold list spec generation' do
8
+ let(:klass) { RiderKick::ScaffoldGenerator }
9
+
10
+ it 'generates list spec with resource_owner_id' do
11
+ stub_const('Models', Module.new)
12
+ stub_const('Models::Article', Class.new do
13
+ def self.columns
14
+ [
15
+ Struct.new(:name, :type).new('id', :integer),
16
+ Struct.new(:name, :type).new('title', :string),
17
+ Struct.new(:name, :type).new('content', :text),
18
+ Struct.new(:name, :type).new('account_id', :string),
19
+ Struct.new(:name, :type).new('user_id', :integer),
20
+ Struct.new(:name, :type).new('created_at', :datetime),
21
+ Struct.new(:name, :type).new('updated_at', :datetime)
22
+ ]
23
+ end
24
+
25
+ def self.column_names
26
+ ['id', 'title', 'content', 'account_id', 'user_id', 'created_at', 'updated_at']
27
+ end
28
+
29
+ def self.columns_hash
30
+ {
31
+ 'title' => Struct.new(:type).new(:string),
32
+ 'content' => Struct.new(:type).new(:text),
33
+ 'account_id' => Struct.new(:type).new(:string),
34
+ 'user_id' => Struct.new(:type).new(:integer)
35
+ }
36
+ end
37
+ end)
38
+
39
+ Dir.mktmpdir do |dir|
40
+ Dir.chdir(dir) do
41
+ FileUtils.mkdir_p('app/domains')
42
+ FileUtils.mkdir_p('db/structures')
43
+
44
+ # Create structure YAML
45
+ structure_yaml = <<~YAML
46
+ model: Models::Article
47
+ resource_name: article
48
+ actor: owner
49
+ resource_owner_id: account_id
50
+ domains:
51
+ action_list:
52
+ use_case:
53
+ contract: []
54
+ repository:
55
+ filters:
56
+ - "{ field: 'title', type: 'search' }"
57
+ YAML
58
+
59
+ File.write('db/structures/articles_structure.yaml', structure_yaml)
60
+
61
+ instance = klass.new(['articles', { 'scope' => 'dashboard' }])
62
+
63
+ # Mock methods
64
+ allow(instance).to receive(:say)
65
+ allow(instance).to receive(:empty_directory)
66
+ allow(RiderKick.configuration).to receive(:domains_path).and_return('app/domains')
67
+
68
+ instance.generate_use_case
69
+
70
+ list_spec_file = 'app/domains/repositories/articles/list_article_spec.rb'
71
+ expect(File.exist?(list_spec_file)).to be true
72
+
73
+ content = File.read(list_spec_file)
74
+
75
+ # Verify format matches user's requirements
76
+ expect(content).to include('let(:repository) { described_class }')
77
+ expect(content).to include('repository.new(params: params).call')
78
+ expect(content).to include('account_id: SecureRandom.uuid')
79
+ expect(content).to include('create_list(:article, 3, account_id: params[:account_id])')
80
+ expect(content).to include('context \'with search filters\'')
81
+ expect(content).to include('context \'with resource owner filter\'')
82
+ expect(content).to include('context \'with sorting\'')
83
+ end
84
+ end
85
+ end
86
+
87
+ it 'generates list spec without resource_owner_id' do
88
+ stub_const('Models', Module.new)
89
+ stub_const('Models::Product', Class.new do
90
+ def self.columns
91
+ [
92
+ Struct.new(:name, :type).new('id', :integer),
93
+ Struct.new(:name, :type).new('name', :string),
94
+ Struct.new(:name, :type).new('price', :decimal),
95
+ Struct.new(:name, :type).new('created_at', :datetime),
96
+ Struct.new(:name, :type).new('updated_at', :datetime)
97
+ ]
98
+ end
99
+
100
+ def self.column_names
101
+ ['id', 'name', 'price', 'created_at', 'updated_at']
102
+ end
103
+
104
+ def self.columns_hash
105
+ {
106
+ 'name' => double(type: :string),
107
+ 'price' => double(type: :decimal)
108
+ }
109
+ end
110
+ end)
111
+
112
+ Dir.mktmpdir do |dir|
113
+ Dir.chdir(dir) do
114
+ FileUtils.mkdir_p('app/domains')
115
+ FileUtils.mkdir_p('db/structures')
116
+
117
+ structure_yaml = <<~YAML
118
+ model: Models::Product
119
+ resource_name: product
120
+ actor: admin
121
+ domains:
122
+ action_list:
123
+ use_case:
124
+ contract: []
125
+ repository:
126
+ filters: []
127
+ YAML
128
+
129
+ File.write('db/structures/products_structure.yaml', structure_yaml)
130
+
131
+ instance = klass.new(['products', { 'scope' => 'admin' }])
132
+
133
+ allow(instance).to receive(:say)
134
+ allow(instance).to receive(:empty_directory)
135
+ allow(RiderKick.configuration).to receive(:domains_path).and_return('app/domains')
136
+
137
+ instance.generate_use_case
138
+
139
+ list_spec_file = 'app/domains/repositories/products/list_product_spec.rb'
140
+ expect(File.exist?(list_spec_file)).to be true
141
+
142
+ content = File.read(list_spec_file)
143
+
144
+ # Verify format without resource_owner_id
145
+ expect(content).to include('let(:repository) { described_class }')
146
+ expect(content).to include('repository.new(params: params).call')
147
+ expect(content).to include('create_list(:product, 3)')
148
+ expect(content).not_to include('account_id')
149
+ expect(content).not_to include('user_id')
150
+ end
151
+ end
152
+ end
153
+ end