tng 0.3.8 → 0.3.9
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/bin/tng +390 -18
- data/binaries/go-ui-darwin-amd64 +0 -0
- data/binaries/go-ui-darwin-arm64 +0 -0
- data/binaries/go-ui-linux-amd64 +0 -0
- data/binaries/go-ui-linux-arm64 +0 -0
- data/binaries/tng-linux-arm64.so +0 -0
- data/binaries/tng-linux-x86_64.so +0 -0
- data/binaries/tng.bundle +0 -0
- data/lib/tng/services/direct_generation.rb +40 -12
- data/lib/tng/services/fix_orchestrator.rb +92 -0
- data/lib/tng/services/repair_service.rb +48 -0
- data/lib/tng/services/test_generator.rb +87 -5
- data/lib/tng/ui/go_ui_session.rb +27 -4
- data/lib/tng/utils.rb +25 -2
- data/lib/tng/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8caa50f15bf264b0c8c1ec375469881d00c1137385f127c28c1e4a8a18bb9554
|
|
4
|
+
data.tar.gz: 56c8dde536a3b54f1e8d0020aae0093b9014b749af79511df5903f920f66c6dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 185d1c9670f66fdd3eb2f469c5109bbdf04c8ca2157c9de3983cd332972dc30c8038926b95013a8a1a2bc6b21d12be9a3f8efb060787936a97202bc0d526d8a1
|
|
7
|
+
data.tar.gz: 4374f5134a64c7b3c5c3d0b4a9b4bd785c555b0932a7552d285b0aa74ebadd1745a7b0395dcaef257426cbbc3e5328d2ca026fa9650d78147cbd4d5c48971801
|
data/bin/tng
CHANGED
|
@@ -10,6 +10,7 @@ require "tng"
|
|
|
10
10
|
require "tng/services/direct_generation"
|
|
11
11
|
require "tng/services/extract_methods"
|
|
12
12
|
require "tng/services/file_type_detector"
|
|
13
|
+
require "tng/services/fix_orchestrator"
|
|
13
14
|
|
|
14
15
|
require "tty-screen"
|
|
15
16
|
require "tty-option"
|
|
@@ -57,6 +58,18 @@ class CLI
|
|
|
57
58
|
desc "Method name (for per-method tests)"
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
flag :audit do
|
|
62
|
+
short "-a"
|
|
63
|
+
long "--audit"
|
|
64
|
+
desc "Run in audit mode (find issues and behaviours instead of generating tests)"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
flag :fix do
|
|
68
|
+
short "-x"
|
|
69
|
+
long "--fix"
|
|
70
|
+
desc "Run in fix mode (automatically repair syntax, lint, and test errors)"
|
|
71
|
+
end
|
|
72
|
+
|
|
60
73
|
flag :help do
|
|
61
74
|
short "-h"
|
|
62
75
|
long "--help"
|
|
@@ -90,7 +103,9 @@ class CLI
|
|
|
90
103
|
|
|
91
104
|
initialize_config_and_clients if rails_loaded
|
|
92
105
|
|
|
93
|
-
if params[:
|
|
106
|
+
if params[:fix]
|
|
107
|
+
handle_fix_command
|
|
108
|
+
elsif params[:file]
|
|
94
109
|
run_direct_generation
|
|
95
110
|
else
|
|
96
111
|
if find_rails_root && !rails_loaded && !defined?(Rails)
|
|
@@ -121,6 +136,10 @@ class CLI
|
|
|
121
136
|
normalized << "--file=#{::Regexp.last_match(2)}"
|
|
122
137
|
when /^(?:--)?(method|m)=(.+)$/
|
|
123
138
|
normalized << "--method=#{::Regexp.last_match(2)}"
|
|
139
|
+
when /^(?:--)?(audit|a)$/
|
|
140
|
+
normalized << "--audit"
|
|
141
|
+
when /^(?:--)?(fix|x)$/
|
|
142
|
+
normalized << "--fix"
|
|
124
143
|
when /^(help|h)=(.+)$/
|
|
125
144
|
normalized << "--help=#{::Regexp.last_match(2)}"
|
|
126
145
|
when /^--file$/, /^-f$/
|
|
@@ -151,8 +170,11 @@ class CLI
|
|
|
151
170
|
arg = positional_args[0]
|
|
152
171
|
has_file = normalized.any? { |a| a.match?(/^--file/) }
|
|
153
172
|
has_method = normalized.any? { |a| a.match?(/^--method/) }
|
|
173
|
+
has_fix = normalized.any? { |a| a.match?(/^--fix/) }
|
|
154
174
|
|
|
155
|
-
if
|
|
175
|
+
if has_fix && !has_file
|
|
176
|
+
normalized << "--file=#{arg}"
|
|
177
|
+
elsif !has_file && (arg.end_with?(".rb") || arg.include?("/"))
|
|
156
178
|
normalized << "--file=#{arg}"
|
|
157
179
|
elsif !has_method && !arg.include?("/")
|
|
158
180
|
normalized << "--method=#{arg}"
|
|
@@ -171,6 +193,8 @@ class CLI
|
|
|
171
193
|
case choice
|
|
172
194
|
when "tests"
|
|
173
195
|
handle_test_generation
|
|
196
|
+
when "audit"
|
|
197
|
+
handle_audit_method
|
|
174
198
|
when "stats"
|
|
175
199
|
user_stats
|
|
176
200
|
when "about"
|
|
@@ -201,6 +225,332 @@ class CLI
|
|
|
201
225
|
end
|
|
202
226
|
end
|
|
203
227
|
|
|
228
|
+
def handle_audit_method
|
|
229
|
+
choice = @go_ui.show_test_type_menu("audit")
|
|
230
|
+
|
|
231
|
+
case choice
|
|
232
|
+
when "controller"
|
|
233
|
+
audit_controller_method
|
|
234
|
+
when "model"
|
|
235
|
+
audit_model_method
|
|
236
|
+
when "service"
|
|
237
|
+
audit_service_method
|
|
238
|
+
when "other"
|
|
239
|
+
audit_other_method
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def audit_controller_method
|
|
244
|
+
controllers = nil
|
|
245
|
+
|
|
246
|
+
@go_ui.show_spinner("Analyzing controllers...") do
|
|
247
|
+
controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
|
|
248
|
+
relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
|
|
249
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
250
|
+
{ name: namespaced_name, path: file[:path] }
|
|
251
|
+
end
|
|
252
|
+
{ success: true, message: "Found #{controllers.length} controllers" }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if controllers.empty?
|
|
256
|
+
@go_ui.show_no_items("controllers")
|
|
257
|
+
return
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
items = controllers.map { |c| { name: c[:name], path: c[:path] } }
|
|
261
|
+
|
|
262
|
+
loop do
|
|
263
|
+
selected_name = @go_ui.show_list_view("Select Controller to Audit", items)
|
|
264
|
+
return if selected_name == "back"
|
|
265
|
+
|
|
266
|
+
controller_choice = controllers.find { |c| c[:name] == selected_name }
|
|
267
|
+
next unless controller_choice
|
|
268
|
+
|
|
269
|
+
show_controller_audit_method_selection(controller_choice)
|
|
270
|
+
# If we return here, it means user pressed back from method selection
|
|
271
|
+
# Loop will show controller list again
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def show_controller_audit_method_selection(controller)
|
|
276
|
+
methods = extract_controller_methods(controller)
|
|
277
|
+
|
|
278
|
+
if methods.empty?
|
|
279
|
+
@go_ui.show_no_items("methods in #{controller[:name]}")
|
|
280
|
+
return
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
items = methods.map { |m| { name: m[:name], path: controller[:name] } }
|
|
284
|
+
selected_name = @go_ui.show_list_view("Select Method to Audit", items)
|
|
285
|
+
return if selected_name == "back"
|
|
286
|
+
|
|
287
|
+
method_choice = methods.find { |m| m[:name] == selected_name }
|
|
288
|
+
return unless method_choice
|
|
289
|
+
|
|
290
|
+
run_audit_for_controller_method(controller, method_choice)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def run_audit_for_controller_method(controller, method_info)
|
|
294
|
+
result = nil
|
|
295
|
+
|
|
296
|
+
@go_ui.show_progress("Auditing #{controller[:name]}##{method_info[:name]}") do |progress|
|
|
297
|
+
progress.update("Analyzing method context...", 25)
|
|
298
|
+
progress.update("Running logical analysis...", 50)
|
|
299
|
+
progress.update("Detecting issues and behaviours...", 75)
|
|
300
|
+
|
|
301
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_controller_method(
|
|
302
|
+
controller, method_info, progress: progress
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
progress.update("Processing results...", 100)
|
|
306
|
+
|
|
307
|
+
if result&.dig(:error)
|
|
308
|
+
{ message: result[:message] || "Audit failed", error: result[:error] }
|
|
309
|
+
else
|
|
310
|
+
{ message: "Audit complete!" }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
|
|
315
|
+
|
|
316
|
+
display_audit_results(result)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def display_audit_results(result)
|
|
320
|
+
audit_data = result[:audit_results]
|
|
321
|
+
|
|
322
|
+
# Save to JSON for persistence
|
|
323
|
+
File.write("audit.json", JSON.pretty_generate(audit_data))
|
|
324
|
+
puts @pastel.green("💾 Audit results saved to audit.json")
|
|
325
|
+
|
|
326
|
+
# Send entire audit_data to go-ui (includes issues, behaviours, method_name, class_name, method_source_with_lines)
|
|
327
|
+
@go_ui.show_audit_results(audit_data, "issues")
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Placeholder methods for other component types
|
|
331
|
+
def audit_model_method
|
|
332
|
+
models = nil
|
|
333
|
+
|
|
334
|
+
@go_ui.show_spinner("Analyzing models...") do
|
|
335
|
+
models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
|
|
336
|
+
relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
|
|
337
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
338
|
+
{ name: namespaced_name, path: file[:path] }
|
|
339
|
+
end
|
|
340
|
+
{ success: true, message: "Found #{models.length} models" }
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
if models.empty?
|
|
344
|
+
@go_ui.show_no_items("models")
|
|
345
|
+
return
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
items = models.map { |m| { name: m[:name], path: m[:path] } }
|
|
349
|
+
|
|
350
|
+
loop do
|
|
351
|
+
selected_name = @go_ui.show_list_view("Select Model to Audit", items)
|
|
352
|
+
return if selected_name == "back"
|
|
353
|
+
|
|
354
|
+
model_choice = models.find { |m| m[:name] == selected_name }
|
|
355
|
+
next unless model_choice
|
|
356
|
+
|
|
357
|
+
show_model_audit_method_selection(model_choice)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def show_model_audit_method_selection(model)
|
|
362
|
+
methods = extract_model_methods(model)
|
|
363
|
+
|
|
364
|
+
if methods.empty?
|
|
365
|
+
@go_ui.show_no_items("methods in #{model[:name]}")
|
|
366
|
+
return
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
items = methods.map { |m| { name: m[:name], path: model[:name] } }
|
|
370
|
+
selected_name = @go_ui.show_list_view("Select Method to Audit", items)
|
|
371
|
+
return if selected_name == "back"
|
|
372
|
+
|
|
373
|
+
method_choice = methods.find { |m| m[:name] == selected_name }
|
|
374
|
+
return unless method_choice
|
|
375
|
+
|
|
376
|
+
run_audit_for_model_method(model, method_choice)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def run_audit_for_model_method(model, method_info)
|
|
380
|
+
result = nil
|
|
381
|
+
|
|
382
|
+
@go_ui.show_progress("Auditing #{model[:name]}##{method_info[:name]}") do |progress|
|
|
383
|
+
progress.update("Parsing method details...", 25)
|
|
384
|
+
progress.update("Analyzing method context...", 25)
|
|
385
|
+
progress.update("Running logical analysis...", 50)
|
|
386
|
+
progress.update("Detecting issues and behaviours...", 75)
|
|
387
|
+
|
|
388
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_model_method(
|
|
389
|
+
model, method_info, progress: progress
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
progress.update("Processing results...", 100)
|
|
393
|
+
|
|
394
|
+
if result&.dig(:error)
|
|
395
|
+
{ message: result[:message] || "Audit failed", error: result[:error] }
|
|
396
|
+
else
|
|
397
|
+
{ message: "Audit complete!" }
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
|
|
402
|
+
|
|
403
|
+
display_audit_results(result)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def audit_service_method
|
|
407
|
+
services = nil
|
|
408
|
+
|
|
409
|
+
@go_ui.show_spinner("Analyzing services...") do
|
|
410
|
+
services = Tng::Analyzers::Service.files_in_dir.map do |file|
|
|
411
|
+
relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
|
|
412
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
413
|
+
{ name: namespaced_name, path: file[:path] }
|
|
414
|
+
end
|
|
415
|
+
{ success: true, message: "Found #{services.length} services" }
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
if services.empty?
|
|
419
|
+
@go_ui.show_no_items("services")
|
|
420
|
+
return
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
items = services.map { |s| { name: s[:name], path: s[:path] } }
|
|
424
|
+
|
|
425
|
+
loop do
|
|
426
|
+
selected_name = @go_ui.show_list_view("Select Service to Audit", items)
|
|
427
|
+
return if selected_name == "back"
|
|
428
|
+
|
|
429
|
+
service_choice = services.find { |s| s[:name] == selected_name }
|
|
430
|
+
next unless service_choice
|
|
431
|
+
|
|
432
|
+
show_service_audit_method_selection(service_choice)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def show_service_audit_method_selection(service)
|
|
437
|
+
methods = extract_service_methods(service)
|
|
438
|
+
|
|
439
|
+
if methods.empty?
|
|
440
|
+
@go_ui.show_no_items("methods in #{service[:name]}")
|
|
441
|
+
return
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
items = methods.map { |m| { name: m[:name], path: service[:name] } }
|
|
445
|
+
selected_name = @go_ui.show_list_view("Select Method to Audit", items)
|
|
446
|
+
return if selected_name == "back"
|
|
447
|
+
|
|
448
|
+
method_choice = methods.find { |m| m[:name] == selected_name }
|
|
449
|
+
return unless method_choice
|
|
450
|
+
|
|
451
|
+
run_audit_for_service_method(service, method_choice)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def run_audit_for_service_method(service, method_info)
|
|
455
|
+
result = nil
|
|
456
|
+
|
|
457
|
+
@go_ui.show_progress("Auditing #{service[:name]}##{method_info[:name]}") do |progress|
|
|
458
|
+
progress.update("Parsing method details...", 25)
|
|
459
|
+
progress.update("Analyzing method context...", 25)
|
|
460
|
+
progress.update("Running logical analysis...", 50)
|
|
461
|
+
progress.update("Detecting issues and behaviours...", 75)
|
|
462
|
+
|
|
463
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_service_method(
|
|
464
|
+
service, method_info, progress: progress
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
progress.update("Processing results...", 100)
|
|
468
|
+
|
|
469
|
+
if result&.dig(:error)
|
|
470
|
+
{ message: result[:message] || "Audit failed", error: result[:error] }
|
|
471
|
+
else
|
|
472
|
+
{ message: "Audit complete!" }
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
|
|
477
|
+
|
|
478
|
+
display_audit_results(result)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def audit_other_method
|
|
482
|
+
other_files = nil
|
|
483
|
+
|
|
484
|
+
@go_ui.show_spinner("Analyzing other files...") do
|
|
485
|
+
other_files = Tng::Analyzers::Other.files_in_dir.map do |file|
|
|
486
|
+
relative_path = file[:relative_path].gsub(".rb", "")
|
|
487
|
+
namespaced_name = relative_path.split("/").map(&:camelize).join("::")
|
|
488
|
+
|
|
489
|
+
{ name: namespaced_name, path: file[:path], type: file[:type] }
|
|
490
|
+
end
|
|
491
|
+
{ success: true, message: "Found #{other_files.length} other files" }
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
if other_files.empty?
|
|
495
|
+
@go_ui.show_no_items("other files")
|
|
496
|
+
return
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
items = other_files.map { |f| { name: f[:name], path: f[:path] } }
|
|
500
|
+
selected_name = @go_ui.show_list_view("Select File", items)
|
|
501
|
+
return if selected_name == "back"
|
|
502
|
+
|
|
503
|
+
other_choice = other_files.find { |f| f[:name] == selected_name }
|
|
504
|
+
return unless other_choice
|
|
505
|
+
|
|
506
|
+
show_audit_other_method_selection(other_choice)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def show_audit_other_method_selection(other_file)
|
|
510
|
+
methods = extract_other_methods(other_file)
|
|
511
|
+
|
|
512
|
+
if methods.empty?
|
|
513
|
+
@go_ui.show_no_items("methods in #{other_file[:name]}")
|
|
514
|
+
return
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
items = methods.map { |m| { name: m[:name], path: other_file[:name] } }
|
|
518
|
+
selected_name = @go_ui.show_list_view("Audit Method in #{other_file[:name]}", items)
|
|
519
|
+
return if selected_name == "back"
|
|
520
|
+
|
|
521
|
+
method_choice = methods.find { |m| m[:name] == selected_name }
|
|
522
|
+
return unless method_choice
|
|
523
|
+
|
|
524
|
+
run_audit_for_other_method(other_file, method_choice)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def run_audit_for_other_method(other_file, method_info)
|
|
528
|
+
result = nil
|
|
529
|
+
|
|
530
|
+
@go_ui.show_progress("Auditing #{other_file[:name]}##{method_info[:name]}") do |progress|
|
|
531
|
+
progress.update("Parsing method details...", 25)
|
|
532
|
+
progress.update("Analyzing method context...", 25)
|
|
533
|
+
progress.update("Running logical analysis...", 50)
|
|
534
|
+
progress.update("Detecting issues and behaviours...", 75)
|
|
535
|
+
|
|
536
|
+
result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_other_method(
|
|
537
|
+
other_file, method_info, progress: progress
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
progress.update("Processing results...", 100)
|
|
541
|
+
|
|
542
|
+
if result&.dig(:error)
|
|
543
|
+
{ message: result[:message] || "Audit failed", error: result[:error] }
|
|
544
|
+
else
|
|
545
|
+
{ message: "Audit complete!" }
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
|
|
550
|
+
|
|
551
|
+
display_audit_results(result)
|
|
552
|
+
end
|
|
553
|
+
|
|
204
554
|
def generate_controller_tests
|
|
205
555
|
controllers = nil
|
|
206
556
|
|
|
@@ -220,13 +570,16 @@ class CLI
|
|
|
220
570
|
end
|
|
221
571
|
|
|
222
572
|
items = controllers.map { |c| { name: c[:name], path: c[:path] } }
|
|
223
|
-
selected_name = @go_ui.show_list_view("Select Controller", items)
|
|
224
|
-
return if selected_name == "back"
|
|
225
573
|
|
|
226
|
-
|
|
227
|
-
|
|
574
|
+
loop do
|
|
575
|
+
selected_name = @go_ui.show_list_view("Select Controller", items)
|
|
576
|
+
return if selected_name == "back"
|
|
577
|
+
|
|
578
|
+
controller_choice = controllers.find { |c| c[:name] == selected_name }
|
|
579
|
+
next unless controller_choice
|
|
228
580
|
|
|
229
|
-
|
|
581
|
+
show_controller_test_options(controller_choice)
|
|
582
|
+
end
|
|
230
583
|
end
|
|
231
584
|
|
|
232
585
|
def generate_model_tests
|
|
@@ -248,13 +601,16 @@ class CLI
|
|
|
248
601
|
end
|
|
249
602
|
|
|
250
603
|
items = models.map { |m| { name: m[:name], path: m[:path] } }
|
|
251
|
-
selected_name = @go_ui.show_list_view("Select Model", items)
|
|
252
|
-
return if selected_name == "back"
|
|
253
604
|
|
|
254
|
-
|
|
255
|
-
|
|
605
|
+
loop do
|
|
606
|
+
selected_name = @go_ui.show_list_view("Select Model", items)
|
|
607
|
+
return if selected_name == "back"
|
|
608
|
+
|
|
609
|
+
model_choice = models.find { |m| m[:name] == selected_name }
|
|
610
|
+
next unless model_choice
|
|
256
611
|
|
|
257
|
-
|
|
612
|
+
show_model_test_options(model_choice)
|
|
613
|
+
end
|
|
258
614
|
end
|
|
259
615
|
|
|
260
616
|
def generate_service_tests
|
|
@@ -276,13 +632,16 @@ class CLI
|
|
|
276
632
|
end
|
|
277
633
|
|
|
278
634
|
items = services.map { |s| { name: s[:name], path: s[:path] } }
|
|
279
|
-
selected_name = @go_ui.show_list_view("Select Service", items)
|
|
280
|
-
return if selected_name == "back"
|
|
281
635
|
|
|
282
|
-
|
|
283
|
-
|
|
636
|
+
loop do
|
|
637
|
+
selected_name = @go_ui.show_list_view("Select Service", items)
|
|
638
|
+
return if selected_name == "back"
|
|
284
639
|
|
|
285
|
-
|
|
640
|
+
service_choice = services.find { |s| s[:name] == selected_name }
|
|
641
|
+
next unless service_choice
|
|
642
|
+
|
|
643
|
+
show_service_test_options(service_choice)
|
|
644
|
+
end
|
|
286
645
|
end
|
|
287
646
|
|
|
288
647
|
def generate_other_tests
|
|
@@ -623,7 +982,8 @@ class CLI
|
|
|
623
982
|
@testng,
|
|
624
983
|
@http_client,
|
|
625
984
|
params,
|
|
626
|
-
method(:show_post_generation_menu)
|
|
985
|
+
method(:show_post_generation_menu),
|
|
986
|
+
@go_ui
|
|
627
987
|
)
|
|
628
988
|
|
|
629
989
|
direct_generator.run
|
|
@@ -688,6 +1048,18 @@ class CLI
|
|
|
688
1048
|
)
|
|
689
1049
|
@testng = Services::Testng.new(@http_client)
|
|
690
1050
|
end
|
|
1051
|
+
|
|
1052
|
+
def handle_fix_command
|
|
1053
|
+
file_path = params[:file]
|
|
1054
|
+
unless file_path
|
|
1055
|
+
puts @pastel.red("❌ File parameter is required for fix mode")
|
|
1056
|
+
puts @pastel.yellow("Usage: bundle exec tng --fix --file=your_file.rb")
|
|
1057
|
+
return
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
orchestrator = Tng::Services::FixOrchestrator.new(@pastel, @http_client)
|
|
1061
|
+
orchestrator.run(file_path)
|
|
1062
|
+
end
|
|
691
1063
|
end
|
|
692
1064
|
|
|
693
1065
|
cli = CLI.new
|
data/binaries/go-ui-darwin-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-darwin-arm64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-arm64
CHANGED
|
Binary file
|
data/binaries/tng-linux-arm64.so
CHANGED
|
Binary file
|
|
Binary file
|
data/binaries/tng.bundle
CHANGED
|
Binary file
|
|
@@ -8,16 +8,29 @@ module Tng
|
|
|
8
8
|
class DirectGeneration
|
|
9
9
|
include ExtractMethods
|
|
10
10
|
|
|
11
|
-
def initialize(pastel, testng, http_client, params, show_post_generation_menu_proc)
|
|
11
|
+
def initialize(pastel, testng, http_client, params, show_post_generation_menu_proc, go_ui)
|
|
12
12
|
@pastel = pastel
|
|
13
13
|
@testng = testng
|
|
14
14
|
@http_client = http_client
|
|
15
15
|
@params = params
|
|
16
16
|
@show_post_generation_menu = show_post_generation_menu_proc
|
|
17
|
+
@go_ui = go_ui
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# ... (rest of methods)
|
|
21
|
+
|
|
22
|
+
def display_audit_results(result)
|
|
23
|
+
audit_results = result[:audit_results]
|
|
24
|
+
return unless audit_results
|
|
25
|
+
|
|
26
|
+
# Use Go UI to display rich audit results
|
|
27
|
+
# "issues" type handles both issues and behaviours in the unified view
|
|
28
|
+
@go_ui.show_audit_results(audit_results, "issues")
|
|
17
29
|
end
|
|
18
30
|
|
|
19
31
|
def run
|
|
20
|
-
file_path
|
|
32
|
+
file_path = @params[:file]
|
|
33
|
+
method_name = @params[:method]
|
|
21
34
|
|
|
22
35
|
unless file_path && method_name
|
|
23
36
|
puts @pastel.red("❌ Both file and method parameters are required")
|
|
@@ -42,7 +55,7 @@ module Tng
|
|
|
42
55
|
private
|
|
43
56
|
|
|
44
57
|
def suggest_similar_files(file_path)
|
|
45
|
-
base_name = File.basename(file_path,
|
|
58
|
+
base_name = File.basename(file_path, ".rb")
|
|
46
59
|
puts @pastel.yellow("💡 Did you mean one of these?")
|
|
47
60
|
|
|
48
61
|
similar_files = find_similar_files(base_name)
|
|
@@ -61,8 +74,8 @@ module Tng
|
|
|
61
74
|
%w[app/controllers app/models app/services app/service].each do |dir|
|
|
62
75
|
next unless Dir.exist?(File.join(rails_root, dir))
|
|
63
76
|
|
|
64
|
-
Dir.glob(File.join(rails_root, dir,
|
|
65
|
-
similar_files << file.gsub(
|
|
77
|
+
Dir.glob(File.join(rails_root, dir, "**", "*#{base_name}*.rb")).each do |file|
|
|
78
|
+
similar_files << file.gsub(%r{^#{Regexp.escape(rails_root)}/}, "")
|
|
66
79
|
end
|
|
67
80
|
end
|
|
68
81
|
|
|
@@ -87,11 +100,17 @@ module Tng
|
|
|
87
100
|
return
|
|
88
101
|
end
|
|
89
102
|
|
|
90
|
-
puts @pastel.bright_white("🎯 Generating test for #{file_object[:name]}##{method_info[:name]}...")
|
|
103
|
+
puts @pastel.bright_white("🎯 #{@params[:audit] ? "Auditing" : "Generating test for"} #{file_object[:name]}##{method_info[:name]}...")
|
|
91
104
|
|
|
92
105
|
result = generate_test_result(file_object, method_info, type)
|
|
93
106
|
|
|
94
|
-
if result
|
|
107
|
+
if result&.dig(:error)
|
|
108
|
+
puts @pastel.red("❌ #{@params[:audit] ? "Audit" : "Test generation"} failed: #{result[:message]}")
|
|
109
|
+
elsif @params[:audit]
|
|
110
|
+
# Audit mode - display results inline
|
|
111
|
+
display_audit_results(result)
|
|
112
|
+
elsif result && result[:file_path]
|
|
113
|
+
# Test generation mode - show post-generation menu
|
|
95
114
|
@show_post_generation_menu.call(result)
|
|
96
115
|
else
|
|
97
116
|
puts @pastel.red("❌ Failed to generate test")
|
|
@@ -110,11 +129,20 @@ module Tng
|
|
|
110
129
|
def generate_test_result(file_object, method_info, type)
|
|
111
130
|
generator = Tng::Services::TestGenerator.new(@http_client)
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
if @params[:audit]
|
|
133
|
+
# Audit mode - return issues and behaviours
|
|
134
|
+
case type
|
|
135
|
+
when "controller" then generator.run_audit_for_controller_method(file_object, method_info)
|
|
136
|
+
else { error: :unsupported, message: "Audit mode only supports controllers currently" }
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
# Test generation mode
|
|
140
|
+
case type
|
|
141
|
+
when "controller" then generator.run_for_controller_method(file_object, method_info)
|
|
142
|
+
when "model" then generator.run_for_model_method(file_object, method_info)
|
|
143
|
+
when "service" then generator.run_for_service_method(file_object, method_info)
|
|
144
|
+
else generator.run_for_other_method(file_object, method_info)
|
|
145
|
+
end
|
|
118
146
|
end
|
|
119
147
|
end
|
|
120
148
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "repair_service"
|
|
4
|
+
|
|
5
|
+
module Tng
|
|
6
|
+
module Services
|
|
7
|
+
class FixOrchestrator
|
|
8
|
+
MAX_ITERATIONS = 3
|
|
9
|
+
|
|
10
|
+
def initialize(pastel, http_client)
|
|
11
|
+
@pastel = pastel
|
|
12
|
+
@repair_service = RepairService.new(http_client)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(file_path)
|
|
16
|
+
unless File.exist?(file_path)
|
|
17
|
+
puts @pastel.red("❌ File not found: #{file_path}")
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts @pastel.bright_white("🔧 Starting Auto-Fix loop for #{file_path}...")
|
|
22
|
+
|
|
23
|
+
all_healthy = false
|
|
24
|
+
MAX_ITERATIONS.times do |i|
|
|
25
|
+
puts @pastel.dim("\n--- Iteration #{i + 1} ---")
|
|
26
|
+
|
|
27
|
+
# 1. Syntax Check
|
|
28
|
+
status = Tng::Utils.validate_ruby_syntax(file_path)
|
|
29
|
+
unless status[:success]
|
|
30
|
+
puts @pastel.yellow("⚠️ Syntax error detected!")
|
|
31
|
+
apply_fix(file_path, :syntax, status[:output])
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
puts @pastel.green("✓ Syntax OK")
|
|
35
|
+
|
|
36
|
+
# 2. Rubocop Check
|
|
37
|
+
status = Tng::Utils.validate_rubocop(file_path)
|
|
38
|
+
unless status[:success]
|
|
39
|
+
puts @pastel.yellow("⚠️ Rubocop violations detected!")
|
|
40
|
+
apply_fix(file_path, :lint, status[:output])
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
puts @pastel.green("✓ Rubocop OK")
|
|
44
|
+
|
|
45
|
+
# 3. Test Check (Optional/Proactive)
|
|
46
|
+
status = Tng::Utils.run_tests(file_path)
|
|
47
|
+
unless status[:success]
|
|
48
|
+
puts @pastel.yellow("⚠️ Test failures detected!")
|
|
49
|
+
apply_fix(file_path, :runtime, status[:output])
|
|
50
|
+
next
|
|
51
|
+
end
|
|
52
|
+
puts @pastel.green("✓ Tests Passed")
|
|
53
|
+
|
|
54
|
+
all_healthy = true
|
|
55
|
+
break
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if all_healthy
|
|
59
|
+
puts @pastel.bright_green("\n🎉 All checks passed! File is healthy.")
|
|
60
|
+
else
|
|
61
|
+
puts @pastel.red("\n❌ Max iterations reached. File still has issues.")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def apply_fix(file_path, error_type, error_message)
|
|
68
|
+
puts @pastel.blue("🧠 Calling LLM to fix #{error_type}...")
|
|
69
|
+
|
|
70
|
+
context = {
|
|
71
|
+
app_config: Tng::Services::UserAppConfig.config_with_source,
|
|
72
|
+
test_gems: Tng::Utils.has_gem?("rspec") ? ["rspec"] : ["minitest"] # Basic for now
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
result = @repair_service.repair_file(file_path, error_type, error_message, context)
|
|
76
|
+
|
|
77
|
+
if result[:error]
|
|
78
|
+
puts @pastel.red("❌ Repair failed: #{result[:message]}")
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if result["file_content"]
|
|
83
|
+
File.write(file_path, result["file_content"])
|
|
84
|
+
puts @pastel.green("✅ Fix applied: #{result["applied_fixes"]&.join(", ")}")
|
|
85
|
+
puts @pastel.dim("📝 Explanation: #{result["explanation"]}")
|
|
86
|
+
else
|
|
87
|
+
puts @pastel.yellow("⚠️ LLM did not return file content.")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Tng
|
|
7
|
+
module Services
|
|
8
|
+
class RepairService
|
|
9
|
+
REPAIR_PATH = "cli/tng_rails/contents/repair"
|
|
10
|
+
|
|
11
|
+
def initialize(http_client)
|
|
12
|
+
@http_client = http_client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def repair_file(file_path, error_type, error_message, context_info = {})
|
|
16
|
+
file_content = File.read(file_path)
|
|
17
|
+
|
|
18
|
+
payload = {
|
|
19
|
+
file_path: file_path,
|
|
20
|
+
file_content: file_content,
|
|
21
|
+
error_type: error_type,
|
|
22
|
+
error_message: error_message,
|
|
23
|
+
context_info: context_info
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Compress payload
|
|
27
|
+
json_payload = payload.to_json
|
|
28
|
+
compressed_payload = Zlib::Deflate.deflate(json_payload)
|
|
29
|
+
|
|
30
|
+
response = @http_client.post_binary(REPAIR_PATH, compressed_payload)
|
|
31
|
+
|
|
32
|
+
return { error: :network_error, message: "Network error" } if response.is_a?(HTTPX::ErrorResponse)
|
|
33
|
+
return { error: :auth_failed, message: "Auth failed" } if [401, 403].include?(response.status)
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
data = JSON.parse(response.body)
|
|
37
|
+
if data["error"]
|
|
38
|
+
{ error: :server_error, message: data["error"] }
|
|
39
|
+
else
|
|
40
|
+
data["result"] # This contains { file_content, explanation, applied_fixes }
|
|
41
|
+
end
|
|
42
|
+
rescue JSON::ParserError => e
|
|
43
|
+
{ error: :parse_error, message: "Failed to parse repair response: #{e.message}" }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -36,6 +36,70 @@ module Tng
|
|
|
36
36
|
generate_test_for_type(other_file, method_info, :other, progress: progress)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Audit mode - async, polls for completion
|
|
40
|
+
def run_audit_for_controller_method(controller, method_info, progress: nil)
|
|
41
|
+
run_audit_for_type(controller, method_info, :controller, progress: progress)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def run_audit_for_model_method(model, method_info, progress: nil)
|
|
45
|
+
run_audit_for_type(model, method_info, :model, progress: progress)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def run_audit_for_service_method(service, method_info, progress: nil)
|
|
49
|
+
run_audit_for_type(service, method_info, :service, progress: progress)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def run_audit_for_other_method(other_file, method_info, progress: nil)
|
|
53
|
+
run_audit_for_type(other_file, method_info, :other, progress: progress)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def run_audit_for_type(file_object, method_info, type, progress: nil)
|
|
57
|
+
response = send_audit_request_for_type(file_object, method_info, type)
|
|
58
|
+
return { error: :network_error, message: "Network error" } unless response
|
|
59
|
+
|
|
60
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
61
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return { error: :auth_failed, message: "Invalid or missing API key" } if response.status == 401
|
|
65
|
+
return { error: :auth_failed, message: "API key expired" } if response.status == 403
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
job_data = JSON.parse(response.body)
|
|
69
|
+
return { error: :server_error, message: job_data["error"] } if job_data["error"]
|
|
70
|
+
|
|
71
|
+
job_id = job_data["job_id"]
|
|
72
|
+
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
73
|
+
|
|
74
|
+
# Poll for completion (similar to test generation)
|
|
75
|
+
result = poll_for_completion(job_id, progress: progress)
|
|
76
|
+
return { error: :timeout, message: "Audit timed out" } unless result
|
|
77
|
+
|
|
78
|
+
# Audit results come back as the result directly (not wrapped)
|
|
79
|
+
# Wrap in audit_results key for save_audit_file compatibility
|
|
80
|
+
{ audit_results: result }
|
|
81
|
+
rescue JSON::ParserError => e
|
|
82
|
+
{ error: :parse_error, message: "Failed to parse audit response: #{e.message}" }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def send_audit_request_for_type(file_object, method_info, type)
|
|
87
|
+
config = request_config
|
|
88
|
+
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
89
|
+
|
|
90
|
+
# Pass audit_mode: true as 8th parameter
|
|
91
|
+
case type
|
|
92
|
+
when :controller
|
|
93
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, true)
|
|
94
|
+
when :model
|
|
95
|
+
Tng.send_request_for_model(name, file_object[:path], method_info, *config, true)
|
|
96
|
+
when :service
|
|
97
|
+
Tng.send_request_for_service(name, file_object[:path], method_info, *config, true)
|
|
98
|
+
when :other
|
|
99
|
+
Tng.send_request_for_other(name, file_object[:path], method_info, *config, true)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
39
103
|
def generate_test_for_type(file_object, method_info, type, progress: nil)
|
|
40
104
|
start_time = Time.now
|
|
41
105
|
|
|
@@ -77,15 +141,16 @@ module Tng
|
|
|
77
141
|
config = request_config
|
|
78
142
|
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
79
143
|
|
|
144
|
+
# Pass audit_mode: false as 8th parameter for normal test generation
|
|
80
145
|
case type
|
|
81
146
|
when :controller
|
|
82
|
-
Tng.send_request_for_controller(name, file_object[:path], method_info, *config)
|
|
147
|
+
Tng.send_request_for_controller(name, file_object[:path], method_info, *config, false)
|
|
83
148
|
when :model
|
|
84
|
-
Tng.send_request_for_model(name, file_object[:path], method_info, *config)
|
|
149
|
+
Tng.send_request_for_model(name, file_object[:path], method_info, *config, false)
|
|
85
150
|
when :service
|
|
86
|
-
Tng.send_request_for_service(name, file_object[:path], method_info, *config)
|
|
151
|
+
Tng.send_request_for_service(name, file_object[:path], method_info, *config, false)
|
|
87
152
|
when :other
|
|
88
|
-
Tng.send_request_for_other(name, file_object[:path], method_info, *config)
|
|
153
|
+
Tng.send_request_for_other(name, file_object[:path], method_info, *config, false)
|
|
89
154
|
end
|
|
90
155
|
end
|
|
91
156
|
|
|
@@ -98,6 +163,7 @@ module Tng
|
|
|
98
163
|
]
|
|
99
164
|
end
|
|
100
165
|
|
|
166
|
+
# Modified method signature and added interrupt handler
|
|
101
167
|
def poll_for_completion(job_id, progress: nil)
|
|
102
168
|
start_time = Time.current
|
|
103
169
|
attempts = 0
|
|
@@ -229,10 +295,26 @@ module Tng
|
|
|
229
295
|
next
|
|
230
296
|
end
|
|
231
297
|
rescue JSON::ParserError => e
|
|
232
|
-
debug_log("
|
|
298
|
+
debug_log("JSON parse error: #{e.message}") if debug_enabled?
|
|
233
299
|
next
|
|
234
300
|
end
|
|
235
301
|
end
|
|
302
|
+
rescue Interrupt
|
|
303
|
+
# User pressed Ctrl+C - exit gracefully
|
|
304
|
+
system("clear") || system("cls")
|
|
305
|
+
puts "\n\n#{TTY::Box.frame(
|
|
306
|
+
"Generation cancelled by user",
|
|
307
|
+
padding: 1,
|
|
308
|
+
align: :center,
|
|
309
|
+
border: :thick,
|
|
310
|
+
style: {
|
|
311
|
+
fg: :yellow,
|
|
312
|
+
border: {
|
|
313
|
+
fg: :yellow
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
)}"
|
|
317
|
+
exit(0)
|
|
236
318
|
end
|
|
237
319
|
|
|
238
320
|
def trigger_cleanup(job_id)
|
data/lib/tng/ui/go_ui_session.rb
CHANGED
|
@@ -47,14 +47,15 @@ module Tng
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Show test type selection menu
|
|
50
|
+
# @param mode [String] "test" or "audit" (default: "test")
|
|
50
51
|
# Returns: "controller", "model", "service", "other", "back"
|
|
51
|
-
def show_test_type_menu
|
|
52
|
+
def show_test_type_menu(mode = "test")
|
|
52
53
|
output_file = Tempfile.new(["go_ui_test_type", ".json"])
|
|
53
54
|
output_path = output_file.path
|
|
54
55
|
output_file.close
|
|
55
56
|
|
|
56
57
|
begin
|
|
57
|
-
system(@binary_path, "rails-test-menu", "--output", output_path)
|
|
58
|
+
system(@binary_path, "rails-test-menu", "--output", output_path, "--mode", mode)
|
|
58
59
|
|
|
59
60
|
return "back" unless File.exist?(output_path) && File.size(output_path) > 0
|
|
60
61
|
|
|
@@ -92,7 +93,7 @@ module Tng
|
|
|
92
93
|
# @param message [String] Spinner message
|
|
93
94
|
# @yield Block that returns Hash with :success and :message keys
|
|
94
95
|
# @return [Hash] Result from block
|
|
95
|
-
def show_spinner(message
|
|
96
|
+
def show_spinner(message)
|
|
96
97
|
control_file = Tempfile.new(["go_ui_spinner", ".json"])
|
|
97
98
|
control_path = control_file.path
|
|
98
99
|
control_file.close
|
|
@@ -126,7 +127,7 @@ module Tng
|
|
|
126
127
|
# @param title [String] Progress title
|
|
127
128
|
# @yield [ProgressUpdater] Block receives updater and returns Hash with :message and :result
|
|
128
129
|
# @return [Hash] Result from block
|
|
129
|
-
def show_progress(title
|
|
130
|
+
def show_progress(title)
|
|
130
131
|
control_file = Tempfile.new(["go_ui_progress", ".json"])
|
|
131
132
|
control_path = control_file.path
|
|
132
133
|
control_file.close
|
|
@@ -239,6 +240,28 @@ module Tng
|
|
|
239
240
|
puts "Test results error: #{e.message}"
|
|
240
241
|
end
|
|
241
242
|
|
|
243
|
+
# Show audit results
|
|
244
|
+
# @param audit_result [Hash] Full audit result with items, method_name, class_name, method_source_with_lines
|
|
245
|
+
# @param audit_type [String] "issues" or "behaviours"
|
|
246
|
+
def show_audit_results(audit_result, _audit_type)
|
|
247
|
+
# Always use the unified audit-results command which handles both issues and behaviours
|
|
248
|
+
command = "audit-results"
|
|
249
|
+
|
|
250
|
+
# Send full result object via temp file to avoid CLI argument limits
|
|
251
|
+
data_json = JSON.generate(audit_result)
|
|
252
|
+
|
|
253
|
+
input_file = Tempfile.new(["go_ui_audit", ".json"])
|
|
254
|
+
input_path = input_file.path
|
|
255
|
+
File.write(input_path, data_json)
|
|
256
|
+
input_file.close
|
|
257
|
+
|
|
258
|
+
system(@binary_path, command, "--file", input_path)
|
|
259
|
+
rescue StandardError => e
|
|
260
|
+
puts "Audit results error: #{e.message}"
|
|
261
|
+
ensure
|
|
262
|
+
File.unlink(input_path) if input_path && File.exist?(input_path)
|
|
263
|
+
end
|
|
264
|
+
|
|
242
265
|
# Show authentication error
|
|
243
266
|
# @param message [String] Error message to display
|
|
244
267
|
def show_auth_error(message = "Authentication failed")
|
data/lib/tng/utils.rb
CHANGED
|
@@ -287,7 +287,7 @@ module Tng
|
|
|
287
287
|
end
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
-
|
|
290
|
+
def self.count_test_nodes(node)
|
|
291
291
|
return 0 unless node.respond_to?(:child_nodes)
|
|
292
292
|
|
|
293
293
|
count = 0
|
|
@@ -303,7 +303,7 @@ module Tng
|
|
|
303
303
|
count
|
|
304
304
|
end
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
def self.test_node?(node)
|
|
307
307
|
case node
|
|
308
308
|
when Prism::DefNode
|
|
309
309
|
# Minitest: def test_something
|
|
@@ -456,5 +456,28 @@ module Tng
|
|
|
456
456
|
"#{minutes}m #{remaining_seconds}s"
|
|
457
457
|
end
|
|
458
458
|
end
|
|
459
|
+
|
|
460
|
+
def self.validate_ruby_syntax(file_path)
|
|
461
|
+
output = `ruby -c #{file_path} 2>&1`
|
|
462
|
+
{ success: $?.success?, output: output }
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def self.validate_rubocop(file_path)
|
|
466
|
+
# Run rubocop with JSON output
|
|
467
|
+
output = `bundle exec rubocop #{file_path} --format json 2>&1`
|
|
468
|
+
{ success: $?.success?, output: output }
|
|
469
|
+
rescue StandardError => e
|
|
470
|
+
{ success: false, output: "Rubocop error: #{e.message}" }
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def self.run_tests(file_path)
|
|
474
|
+
command = if file_path.include?("/spec/")
|
|
475
|
+
"bundle exec rspec #{file_path}"
|
|
476
|
+
else
|
|
477
|
+
"bundle exec rails test #{file_path}"
|
|
478
|
+
end
|
|
479
|
+
output = `#{command} 2>&1`
|
|
480
|
+
{ success: $?.success?, output: output, command: command }
|
|
481
|
+
end
|
|
459
482
|
end
|
|
460
483
|
end
|
data/lib/tng/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tng
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ralucab
|
|
@@ -176,6 +176,8 @@ files:
|
|
|
176
176
|
- lib/tng/services/direct_generation.rb
|
|
177
177
|
- lib/tng/services/extract_methods.rb
|
|
178
178
|
- lib/tng/services/file_type_detector.rb
|
|
179
|
+
- lib/tng/services/fix_orchestrator.rb
|
|
180
|
+
- lib/tng/services/repair_service.rb
|
|
179
181
|
- lib/tng/services/test_generator.rb
|
|
180
182
|
- lib/tng/services/testng.rb
|
|
181
183
|
- lib/tng/services/user_app_config.rb
|
|
@@ -217,7 +219,7 @@ post_install_message: "┌ TNG ────────────────
|
|
|
217
219
|
\ │\n│ • bundle exec
|
|
218
220
|
tng --help - Show help information │\n│ │\n│
|
|
219
221
|
\ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
|
|
220
|
-
v0.3.
|
|
222
|
+
v0.3.9 ┘\n"
|
|
221
223
|
rdoc_options: []
|
|
222
224
|
require_paths:
|
|
223
225
|
- lib
|