tng 0.1.6 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +6 -20
- data/bin/tng +238 -472
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/tng/analyzers/model.rb +9 -0
- data/lib/tng/analyzers/other.rb +277 -0
- data/lib/tng/analyzers/service.rb +1 -0
- data/lib/tng/api/http_client.rb +21 -23
- data/lib/tng/services/direct_generation.rb +320 -0
- data/lib/tng/services/extract_methods.rb +39 -0
- data/lib/tng/services/test_generator.rb +232 -104
- data/lib/tng/services/testng.rb +2 -2
- data/lib/tng/services/user_app_config.rb +5 -6
- data/lib/tng/ui/about_display.rb +7 -5
- data/lib/tng/ui/controller_test_flow_display.rb +0 -17
- data/lib/tng/ui/model_test_flow_display.rb +0 -17
- data/lib/tng/ui/other_test_flow_display.rb +78 -0
- data/lib/tng/ui/post_install_box.rb +7 -9
- data/lib/tng/ui/service_test_flow_display.rb +0 -17
- data/lib/tng/ui/show_help.rb +11 -68
- data/lib/tng/utils.rb +67 -5
- data/lib/tng/version.rb +1 -1
- metadata +15 -11
@@ -0,0 +1,320 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "extract_methods"
|
4
|
+
|
5
|
+
module Tng
|
6
|
+
module Services
|
7
|
+
class DirectGeneration
|
8
|
+
include ExtractMethods
|
9
|
+
|
10
|
+
def initialize(pastel, testng, http_client, params, show_post_generation_menu_proc)
|
11
|
+
@pastel = pastel
|
12
|
+
@testng = testng
|
13
|
+
@http_client = http_client
|
14
|
+
@params = params
|
15
|
+
@show_post_generation_menu = show_post_generation_menu_proc
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
type = normalize_type(@params[:type])
|
20
|
+
file_name = @params[:file]
|
21
|
+
method_name = @params[:method]
|
22
|
+
|
23
|
+
unless type && file_name && method_name
|
24
|
+
puts @pastel.red("❌ All parameters are required for direct generation: --type, --file, --method")
|
25
|
+
puts @pastel.yellow("Usage: bundle exec tng --type=controller --file=users --method=index")
|
26
|
+
puts @pastel.yellow("Shortcuts: bundle exec tng -t=c -f=users -m=index")
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
unless %w[controller model service other].include?(type)
|
31
|
+
puts @pastel.red("❌ Invalid type: #{@params[:type]}")
|
32
|
+
puts @pastel.yellow("Supported types: controller, model, service, other")
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
case type
|
37
|
+
when "controller"
|
38
|
+
run_controller_method_generation(file_name, method_name)
|
39
|
+
when "model"
|
40
|
+
run_model_method_generation(file_name, method_name)
|
41
|
+
when "service"
|
42
|
+
run_service_method_generation(file_name, method_name)
|
43
|
+
when "other"
|
44
|
+
run_other_method_generation(file_name, method_name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def normalize_type(type_param)
|
49
|
+
case type_param&.downcase
|
50
|
+
when "c", "controller"
|
51
|
+
"controller"
|
52
|
+
when "m", "mo", "model"
|
53
|
+
"model"
|
54
|
+
when "s", "se", "service"
|
55
|
+
"service"
|
56
|
+
when "o", "other"
|
57
|
+
"other"
|
58
|
+
else
|
59
|
+
type_param&.downcase
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_controller_method_generation(file_name, method_name)
|
64
|
+
controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
|
65
|
+
relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
|
66
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
67
|
+
|
68
|
+
{
|
69
|
+
name: namespaced_name,
|
70
|
+
path: file[:path]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
controller = find_matching_controller(controllers, file_name)
|
75
|
+
|
76
|
+
unless controller
|
77
|
+
puts @pastel.red("❌ Controller not found: #{file_name}")
|
78
|
+
puts @pastel.yellow("Available controllers:")
|
79
|
+
controllers.first(5).each do |ctrl|
|
80
|
+
puts @pastel.dim(" - #{ctrl[:name]}")
|
81
|
+
end
|
82
|
+
puts @pastel.dim(" ... and #{controllers.length - 5} more") if controllers.length > 5
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
methods = extract_controller_methods(controller)
|
87
|
+
|
88
|
+
if methods.empty?
|
89
|
+
puts @pastel.yellow("⚠️ No methods found in #{controller[:name]}")
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
method_info = methods.find { |method| method[:name].downcase == method_name.downcase }
|
94
|
+
|
95
|
+
unless method_info
|
96
|
+
puts @pastel.red("❌ Method '#{method_name}' not found in #{controller[:name]}")
|
97
|
+
puts @pastel.yellow("Available methods:")
|
98
|
+
methods.first(10).each do |method|
|
99
|
+
puts @pastel.dim(" - #{method[:name]}")
|
100
|
+
end
|
101
|
+
puts @pastel.dim(" ... and #{methods.length - 10} more") if methods.length > 10
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
puts @pastel.bright_white("🎯 Generating test for #{controller[:name]}##{method_info[:name]}...")
|
106
|
+
|
107
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_for_controller_method(controller, method_info)
|
108
|
+
|
109
|
+
if result && result[:file_path]
|
110
|
+
@show_post_generation_menu.call(result)
|
111
|
+
else
|
112
|
+
puts @pastel.red("❌ Failed to generate test")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_matching_controller(controllers, file_name)
|
117
|
+
controllers.find do |controller|
|
118
|
+
controller[:name].downcase == file_name.downcase ||
|
119
|
+
controller[:name].split("::").last.downcase == file_name.downcase ||
|
120
|
+
File.basename(controller[:path], ".rb").downcase == file_name.downcase ||
|
121
|
+
File.basename(controller[:path], ".rb").gsub("_controller", "").downcase == file_name.downcase ||
|
122
|
+
File.basename(controller[:path], ".rb").downcase == "#{file_name}_controller".downcase
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_model_method_generation(file_name, method_name)
|
127
|
+
models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
|
128
|
+
relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
|
129
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
130
|
+
|
131
|
+
{
|
132
|
+
name: namespaced_name,
|
133
|
+
path: file[:path]
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
model = find_matching_model(models, file_name)
|
138
|
+
|
139
|
+
unless model
|
140
|
+
puts @pastel.red("❌ Model not found: #{file_name}")
|
141
|
+
puts @pastel.yellow("Available models:")
|
142
|
+
models.first(5).each do |mdl|
|
143
|
+
puts @pastel.dim(" - #{mdl[:name]}")
|
144
|
+
end
|
145
|
+
puts @pastel.dim(" ... and #{models.length - 5} more") if models.length > 5
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
methods = extract_model_methods(model)
|
150
|
+
|
151
|
+
if methods.empty?
|
152
|
+
puts @pastel.yellow("⚠️ No methods found in #{model[:name]}")
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
method_info = methods.find { |method| method[:name].downcase == method_name.downcase }
|
157
|
+
|
158
|
+
unless method_info
|
159
|
+
puts @pastel.red("❌ Method '#{method_name}' not found in #{model[:name]}")
|
160
|
+
puts @pastel.yellow("Available methods:")
|
161
|
+
methods.first(10).each do |method|
|
162
|
+
puts @pastel.dim(" - #{method[:name]}")
|
163
|
+
end
|
164
|
+
puts @pastel.dim(" ... and #{methods.length - 10} more") if methods.length > 10
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
168
|
+
puts @pastel.bright_white("🎯 Generating test for #{model[:name]}##{method_info[:name]}...")
|
169
|
+
|
170
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_for_model_method(model, method_info)
|
171
|
+
|
172
|
+
if result && result[:file_path]
|
173
|
+
@show_post_generation_menu.call(result)
|
174
|
+
else
|
175
|
+
puts @pastel.red("❌ Failed to generate test")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def find_matching_model(models, file_name)
|
180
|
+
models.find do |model|
|
181
|
+
model[:name].downcase == file_name.downcase ||
|
182
|
+
model[:name].split("::").last.downcase == file_name.downcase ||
|
183
|
+
File.basename(model[:path], ".rb").downcase == file_name.downcase
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def run_service_method_generation(file_name, method_name)
|
188
|
+
services = Tng::Analyzers::Service.files_in_dir.map do |file|
|
189
|
+
relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
|
190
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
191
|
+
|
192
|
+
{
|
193
|
+
name: namespaced_name,
|
194
|
+
path: file[:path]
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
service = find_matching_service(services, file_name)
|
199
|
+
|
200
|
+
unless service
|
201
|
+
puts @pastel.red("❌ Service not found: #{file_name}")
|
202
|
+
puts @pastel.yellow("Available services:")
|
203
|
+
services.first(5).each do |svc|
|
204
|
+
puts @pastel.dim(" - #{svc[:name]}")
|
205
|
+
end
|
206
|
+
puts @pastel.dim(" ... and #{services.length - 5} more") if services.length > 5
|
207
|
+
return
|
208
|
+
end
|
209
|
+
|
210
|
+
methods = extract_service_methods(service)
|
211
|
+
|
212
|
+
if methods.empty?
|
213
|
+
puts @pastel.yellow("⚠️ No methods found in #{service[:name]}")
|
214
|
+
return
|
215
|
+
end
|
216
|
+
|
217
|
+
method_info = methods.find { |method| method[:name].downcase == method_name.downcase }
|
218
|
+
|
219
|
+
unless method_info
|
220
|
+
puts @pastel.red("❌ Method '#{method_name}' not found in #{service[:name]}")
|
221
|
+
puts @pastel.yellow("Available methods:")
|
222
|
+
methods.first(10).each do |method|
|
223
|
+
puts @pastel.dim(" - #{method[:name]}")
|
224
|
+
end
|
225
|
+
puts @pastel.dim(" ... and #{methods.length - 10} more") if methods.length > 10
|
226
|
+
return
|
227
|
+
end
|
228
|
+
|
229
|
+
puts @pastel.bright_white("🎯 Generating test for #{service[:name]}##{method_info[:name]}...")
|
230
|
+
|
231
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_for_service_method(service, method_info)
|
232
|
+
|
233
|
+
if result && result[:file_path]
|
234
|
+
@show_post_generation_menu.call(result)
|
235
|
+
else
|
236
|
+
puts @pastel.red("❌ Failed to generate test")
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def find_matching_service(services, file_name)
|
241
|
+
services.find do |service|
|
242
|
+
service[:name].downcase == file_name.downcase ||
|
243
|
+
service[:name].split("::").last.downcase == file_name.downcase ||
|
244
|
+
File.basename(service[:path], ".rb").downcase == file_name.downcase
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def run_other_method_generation(file_name, method_name)
|
249
|
+
other_files = Tng::Analyzers::Other.files_in_dir.map do |file|
|
250
|
+
relative_path = file[:relative_path].gsub(".rb", "")
|
251
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
252
|
+
|
253
|
+
{
|
254
|
+
name: namespaced_name,
|
255
|
+
path: file[:path],
|
256
|
+
type: file[:type]
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
other_file = find_matching_other_file(other_files, file_name)
|
261
|
+
|
262
|
+
unless other_file
|
263
|
+
puts @pastel.red("❌ Other file not found: #{file_name}")
|
264
|
+
puts @pastel.yellow("Available other files:")
|
265
|
+
other_files.first(5).each do |file|
|
266
|
+
puts @pastel.dim(" - #{file[:name]}")
|
267
|
+
end
|
268
|
+
puts @pastel.dim(" ... and #{other_files.length - 5} more") if other_files.length > 5
|
269
|
+
return
|
270
|
+
end
|
271
|
+
|
272
|
+
methods = extract_other_methods(other_file)
|
273
|
+
|
274
|
+
if methods.empty?
|
275
|
+
puts @pastel.yellow("⚠️ No methods found in #{other_file[:name]}")
|
276
|
+
return
|
277
|
+
end
|
278
|
+
|
279
|
+
method_info = methods.find { |method| method[:name].downcase == method_name.downcase }
|
280
|
+
|
281
|
+
unless method_info
|
282
|
+
puts @pastel.red("❌ Method '#{method_name}' not found in #{other_file[:name]}")
|
283
|
+
puts @pastel.yellow("Available methods:")
|
284
|
+
methods.first(10).each do |method|
|
285
|
+
puts @pastel.dim(" - #{method[:name]}")
|
286
|
+
end
|
287
|
+
puts @pastel.dim(" ... and #{methods.length - 10} more") if methods.length > 10
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
puts @pastel.bright_white("🎯 Generating test for #{other_file[:name]}##{method_info[:name]}...")
|
292
|
+
|
293
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_for_other_method(other_file, method_info)
|
294
|
+
|
295
|
+
if result && result[:file_path]
|
296
|
+
@show_post_generation_menu.call(result)
|
297
|
+
else
|
298
|
+
puts @pastel.red("❌ Failed to generate test")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def find_matching_other_file(other_files, file_name)
|
303
|
+
other_files.find do |file|
|
304
|
+
file[:name].downcase == file_name.downcase ||
|
305
|
+
file[:name].split("::").last.downcase == file_name.downcase ||
|
306
|
+
File.basename(file[:path], ".rb").downcase == file_name.downcase ||
|
307
|
+
normalize_other_file_path(file[:path]).downcase == file_name.downcase
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def normalize_other_file_path(file_path)
|
312
|
+
Tng::Analyzers::Other::OTHER_DIRECTORIES.each do |dir|
|
313
|
+
pattern = %r{^.*/#{Regexp.escape(dir)}/}
|
314
|
+
return file_path.gsub(pattern, "").gsub(".rb", "") if file_path.match?(pattern)
|
315
|
+
end
|
316
|
+
file_path # fallback if no match
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tng
|
4
|
+
module Services
|
5
|
+
module ExtractMethods
|
6
|
+
def extract_controller_methods(controller)
|
7
|
+
methods = Tng::Analyzers::Controller.methods_for_controller(controller[:name])
|
8
|
+
Array(methods)
|
9
|
+
rescue StandardError => e
|
10
|
+
puts center_text(@pastel.decorate("#{Tng::UI::Theme.icon(:error)} Error analyzing controller: #{e.message}", Tng::UI::Theme.color(:error)))
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_model_methods(model)
|
15
|
+
methods = Tng::Analyzers::Model.methods_for_model(model[:name])
|
16
|
+
Array(methods)
|
17
|
+
rescue StandardError => e
|
18
|
+
puts center_text(@pastel.decorate("#{Tng::UI::Theme.icon(:error)} Error analyzing model: #{e.message}", Tng::UI::Theme.color(:error)))
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_service_methods(service)
|
23
|
+
methods = Tng::Analyzers::Service.methods_for_service(service[:name])
|
24
|
+
Array(methods)
|
25
|
+
rescue StandardError => e
|
26
|
+
puts center_text(@pastel.decorate("#{Tng::UI::Theme.icon(:error)} Error analyzing service: #{e.message}", Tng::UI::Theme.color(:error)))
|
27
|
+
[]
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_other_methods(other_file)
|
31
|
+
methods = Tng::Analyzers::Other.methods_for_other(other_file[:name], other_file[:path])
|
32
|
+
Array(methods)
|
33
|
+
rescue StandardError => e
|
34
|
+
puts center_text(@pastel.decorate("#{Tng::UI::Theme.icon(:error)} Error analyzing file: #{e.message}", Tng::UI::Theme.color(:error)))
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|