tng 0.5.4 → 0.5.5
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/README.md +19 -0
- data/bin/tng +446 -57
- 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-darwin-arm64.bundle +0 -0
- data/binaries/tng-darwin-x86_64.bundle +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/generators/tng/install_generator.rb +11 -54
- data/lib/tng/api/http_client.rb +0 -2
- data/lib/tng/services/direct_generation.rb +33 -3
- data/lib/tng/services/file_discovery.rb +43 -0
- data/lib/tng/services/file_type_detector.rb +19 -2
- data/lib/tng/services/installer.rb +82 -0
- data/lib/tng/services/test_generator.rb +155 -22
- data/lib/tng/services/testng.rb +3 -1
- data/lib/tng/services/user_app_config.rb +3 -2
- data/lib/tng/ui/go_ui_session.rb +20 -1
- data/lib/tng/ui/post_install_box.rb +3 -3
- data/lib/tng/utils.rb +45 -6
- data/lib/tng/version.rb +1 -1
- data/lib/tng.rb +1 -1
- metadata +5 -3
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
|
|
Binary file
|
|
Binary file
|
data/binaries/tng-linux-arm64.so
CHANGED
|
Binary file
|
|
Binary file
|
data/binaries/tng.bundle
CHANGED
|
Binary file
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "tng/services/installer"
|
|
4
5
|
|
|
5
6
|
module Tng
|
|
6
7
|
module Generators
|
|
@@ -13,68 +14,24 @@ module Tng
|
|
|
13
14
|
if File.exist?(initializer_path)
|
|
14
15
|
say "Configuration file already exists at #{initializer_path}", :yellow
|
|
15
16
|
if yes?("Do you want to overwrite it? (y/n)")
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
Tng::Services::Installer.install(
|
|
18
|
+
project_root: Dir.pwd,
|
|
19
|
+
rails_project: true,
|
|
20
|
+
force: true
|
|
21
|
+
)
|
|
18
22
|
say "TNG configuration file updated!", :green
|
|
19
23
|
else
|
|
20
24
|
say "Skipping configuration file creation.", :blue
|
|
21
25
|
end
|
|
22
26
|
else
|
|
23
|
-
|
|
27
|
+
Tng::Services::Installer.install(
|
|
28
|
+
project_root: Dir.pwd,
|
|
29
|
+
rails_project: true,
|
|
30
|
+
force: false
|
|
31
|
+
)
|
|
24
32
|
say "TNG configuration file created at #{initializer_path}", :green
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def configuration_template
|
|
31
|
-
<<~RUBY
|
|
32
|
-
# frozen_string_literal: true
|
|
33
|
-
|
|
34
|
-
return unless Rails.env.development?
|
|
35
|
-
|
|
36
|
-
Tng.configure do |config|
|
|
37
|
-
# Get your API key from https://app.tng.sh/
|
|
38
|
-
config.api_key = ENV["TNG_API_KEY"]
|
|
39
|
-
config.base_url = ENV["API_BASE_URI"] || "https://app.tng.sh/"
|
|
40
|
-
|
|
41
|
-
# Test Helper File (optional - auto-detected if not set)
|
|
42
|
-
# Uncomment to override auto-detection with a custom path
|
|
43
|
-
# Default auto-detection checks: spec/rails_helper.rb, spec/spec_helper.rb, test/test_helper.rb
|
|
44
|
-
# config.test_helper_path = "spec/rails_helper.rb"
|
|
45
|
-
|
|
46
|
-
# Ignore paths (relative to project root)
|
|
47
|
-
# config.ignore_files = []
|
|
48
|
-
# config.ignore_folders = []
|
|
49
|
-
|
|
50
|
-
# Authentication Configuration
|
|
51
|
-
# Set to false if your application does not require authentication
|
|
52
|
-
# config.authentication_enabled = true
|
|
53
|
-
|
|
54
|
-
# ⚠️ IMPORTANT: AUTHENTICATION CONFIGURATION REQUIRED ⚠️
|
|
55
|
-
# You MUST configure your authentication methods below for TNG to work properly.
|
|
56
|
-
# Uncomment and modify the authentication_methods configuration:
|
|
57
|
-
|
|
58
|
-
# Authentication Methods (multiple methods supported)
|
|
59
|
-
# Supported authentication types: session, devise, jwt, token_auth, basic_auth, oauth, headers, custom, nil
|
|
60
|
-
# EXAMPLE: Uncomment and modify these examples to match your app's authentication:
|
|
61
|
-
|
|
62
|
-
# config.authentication_methods = [
|
|
63
|
-
# {
|
|
64
|
-
# method: "authenticate_user_via_session!",
|
|
65
|
-
# file_location: "app/controllers/application_controller.rb",
|
|
66
|
-
# auth_type: "session"
|
|
67
|
-
# },
|
|
68
|
-
# {
|
|
69
|
-
# method: "authenticate_user_via_api_key!",
|
|
70
|
-
# file_location: "app/controllers/application_controller.rb",
|
|
71
|
-
# auth_type: "headers"
|
|
72
|
-
# }
|
|
73
|
-
# ]
|
|
74
|
-
# ⚠️ Remember to configure your authentication methods above! ⚠️
|
|
75
|
-
end
|
|
76
|
-
RUBY
|
|
77
|
-
end
|
|
78
35
|
end
|
|
79
36
|
end
|
|
80
37
|
end
|
data/lib/tng/api/http_client.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require_relative "extract_methods"
|
|
4
5
|
require_relative "file_type_detector"
|
|
5
6
|
|
|
@@ -18,8 +19,17 @@ module Tng
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def display_audit_results(result)
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
# debug logs removed
|
|
23
|
+
audit_results = result[:audit_results] || result["audit_results"] || result[:result] || result["result"] || result
|
|
24
|
+
if audit_results.is_a?(String)
|
|
25
|
+
begin
|
|
26
|
+
audit_results = JSON.parse(audit_results)
|
|
27
|
+
rescue JSON::ParserError
|
|
28
|
+
audit_results = nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
# debug logs removed
|
|
32
|
+
return unless audit_results.is_a?(Hash)
|
|
23
33
|
|
|
24
34
|
@go_ui.show_audit_results(audit_results, "issues")
|
|
25
35
|
end
|
|
@@ -79,7 +89,15 @@ module Tng
|
|
|
79
89
|
end
|
|
80
90
|
|
|
81
91
|
def find_similar_files(base_name)
|
|
82
|
-
|
|
92
|
+
unless Tng::Utils.rails_project?(Dir.pwd)
|
|
93
|
+
root = Dir.pwd
|
|
94
|
+
files = Tng::Services::FileDiscovery.list_ruby_files(root)
|
|
95
|
+
return files
|
|
96
|
+
.select { |f| File.basename(f, ".rb").include?(base_name) }
|
|
97
|
+
.map { |f| f.gsub(%r{^#{Regexp.escape(root)}/}, "") }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
rails_root = defined?(::Rails) && ::Rails.root ? ::Rails.root.to_s : Dir.pwd
|
|
83
101
|
similar_files = []
|
|
84
102
|
|
|
85
103
|
%w[app/controllers app/models app/services app/service].each do |dir|
|
|
@@ -129,6 +147,7 @@ module Tng
|
|
|
129
147
|
result = @go_ui.show_progress("Processing...") do |progress|
|
|
130
148
|
generate_test_result(file_object, method_info, type, progress)
|
|
131
149
|
end
|
|
150
|
+
# debug logs removed
|
|
132
151
|
|
|
133
152
|
if result&.dig(:error)
|
|
134
153
|
label =
|
|
@@ -185,6 +204,17 @@ module Tng
|
|
|
185
204
|
def generate_test_result(file_object, method_info, type, progress)
|
|
186
205
|
generator = Tng::Services::TestGenerator.new(@http_client)
|
|
187
206
|
|
|
207
|
+
if !Tng::Utils.rails_project?(Dir.pwd)
|
|
208
|
+
method_info = method_info.merge(test_type: method_info[:test_type] || "other")
|
|
209
|
+
return generator.run_for_ruby_method(file_object, method_info, progress: progress) unless @params[:trace] || @params[:audit] || @params[:xray]
|
|
210
|
+
|
|
211
|
+
if @params[:audit]
|
|
212
|
+
return generator.run_audit_for_ruby_method(file_object, method_info, progress: progress)
|
|
213
|
+
elsif @params[:xray]
|
|
214
|
+
return generator.run_xray_for_ruby_method(file_object, method_info, progress: progress)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
188
218
|
if @params[:trace]
|
|
189
219
|
perform_trace_analysis(file_object, method_info, progress)
|
|
190
220
|
elsif @params[:xray]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "find"
|
|
4
|
+
|
|
5
|
+
module Tng
|
|
6
|
+
module Services
|
|
7
|
+
class FileDiscovery
|
|
8
|
+
DEFAULT_EXCLUDE_DIRS = %w[
|
|
9
|
+
test spec features
|
|
10
|
+
tmp log vendor node_modules coverage dist build
|
|
11
|
+
.git db
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def self.list_ruby_files(root = Dir.pwd)
|
|
15
|
+
files = []
|
|
16
|
+
root = File.expand_path(root)
|
|
17
|
+
|
|
18
|
+
Find.find(root) do |path|
|
|
19
|
+
if File.directory?(path)
|
|
20
|
+
name = File.basename(path)
|
|
21
|
+
if DEFAULT_EXCLUDE_DIRS.include?(name)
|
|
22
|
+
Find.prune
|
|
23
|
+
else
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
next unless path.end_with?(".rb")
|
|
29
|
+
next if Tng::Utils.ignored_path?(path)
|
|
30
|
+
|
|
31
|
+
files << path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
files
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.relative_paths(root = Dir.pwd)
|
|
38
|
+
root = File.expand_path(root)
|
|
39
|
+
list_ruby_files(root).map { |p| p.sub(%r{\A#{Regexp.escape(root)}/?}, "") }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
begin
|
|
4
|
+
require "active_support/core_ext/string/inflections"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Optional in non-Rails environments
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "file_discovery"
|
|
4
10
|
|
|
5
11
|
module Tng
|
|
6
12
|
module Services
|
|
@@ -53,6 +59,8 @@ module Tng
|
|
|
53
59
|
}.freeze
|
|
54
60
|
|
|
55
61
|
def detect_type(file_path)
|
|
62
|
+
return "other" unless Tng::Utils.rails_project?(Dir.pwd)
|
|
63
|
+
|
|
56
64
|
normalized_path = normalize_path(file_path)
|
|
57
65
|
TYPE_PATTERNS.find { |pattern, _| normalized_path.match?(pattern) }&.last || 'other'
|
|
58
66
|
end
|
|
@@ -78,7 +86,16 @@ module Tng
|
|
|
78
86
|
candidates << File.expand_path(file_with_ext)
|
|
79
87
|
end
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
unless Tng::Utils.rails_project?(Dir.pwd)
|
|
90
|
+
root = Dir.pwd
|
|
91
|
+
files = Tng::Services::FileDiscovery.list_ruby_files(root)
|
|
92
|
+
files.each do |file|
|
|
93
|
+
candidates << file if File.basename(file) == File.basename(file_with_ext)
|
|
94
|
+
end
|
|
95
|
+
return candidates.uniq
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
rails_root = defined?(::Rails) && ::Rails.root ? ::Rails.root.to_s : Dir.pwd
|
|
82
99
|
|
|
83
100
|
SEARCH_PATHS.each do |dir|
|
|
84
101
|
full_path = File.join(rails_root, dir, file_with_ext)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Tng
|
|
6
|
+
module Services
|
|
7
|
+
class Installer
|
|
8
|
+
def self.configuration_template(rails: false)
|
|
9
|
+
header = if rails
|
|
10
|
+
"return unless Rails.env.development?"
|
|
11
|
+
else
|
|
12
|
+
"if defined?(Rails) && Rails.respond_to?(:env)\n return unless Rails.env.development?\nend"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
<<~RUBY
|
|
16
|
+
# frozen_string_literal: true
|
|
17
|
+
|
|
18
|
+
#{header}
|
|
19
|
+
|
|
20
|
+
Tng.configure do |config|
|
|
21
|
+
# Get your API key from https://app.tng.sh/
|
|
22
|
+
config.api_key = ENV["TNG_API_KEY"]
|
|
23
|
+
config.base_url = ENV["API_BASE_URI"] || "https://app.tng.sh/"
|
|
24
|
+
|
|
25
|
+
# Test Helper File (optional - auto-detected if not set)
|
|
26
|
+
# Uncomment to override auto-detection with a custom path
|
|
27
|
+
# Default auto-detection checks: spec/rails_helper.rb, spec/spec_helper.rb, test/test_helper.rb
|
|
28
|
+
# config.test_helper_path = "spec/rails_helper.rb"
|
|
29
|
+
|
|
30
|
+
# Ignore paths (relative to project root)
|
|
31
|
+
# config.ignore_files = []
|
|
32
|
+
# config.ignore_folders = []
|
|
33
|
+
|
|
34
|
+
# Authentication Configuration
|
|
35
|
+
# Set to false if your application does not require authentication
|
|
36
|
+
# config.authentication_enabled = true
|
|
37
|
+
|
|
38
|
+
# ⚠️ IMPORTANT: AUTHENTICATION CONFIGURATION REQUIRED ⚠️
|
|
39
|
+
# You MUST configure your authentication methods below for TNG to work properly.
|
|
40
|
+
# Uncomment and modify the authentication_methods configuration:
|
|
41
|
+
|
|
42
|
+
# Authentication Methods (multiple methods supported)
|
|
43
|
+
# Supported authentication types: session, devise, jwt, token_auth, basic_auth, oauth, headers, custom, nil
|
|
44
|
+
# EXAMPLE: Uncomment and modify these examples to match your app's authentication:
|
|
45
|
+
|
|
46
|
+
# config.authentication_methods = [
|
|
47
|
+
# {
|
|
48
|
+
# method: "authenticate_user_via_session!",
|
|
49
|
+
# file_location: "app/controllers/application_controller.rb",
|
|
50
|
+
# auth_type: "session"
|
|
51
|
+
# },
|
|
52
|
+
# {
|
|
53
|
+
# method: "authenticate_user_via_api_key!",
|
|
54
|
+
# file_location: "app/controllers/application_controller.rb",
|
|
55
|
+
# auth_type: "headers"
|
|
56
|
+
# }
|
|
57
|
+
# ]
|
|
58
|
+
# ⚠️ Remember to configure your authentication methods above! ⚠️
|
|
59
|
+
end
|
|
60
|
+
RUBY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.install(project_root:, rails_project:, force: false)
|
|
64
|
+
target = if rails_project
|
|
65
|
+
File.join(project_root, "config", "initializers", "tng.rb")
|
|
66
|
+
else
|
|
67
|
+
File.join(project_root, "tng.rb")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if File.exist?(target)
|
|
71
|
+
return [false, target] unless force
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
dir = File.dirname(target)
|
|
75
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
76
|
+
File.write(target, configuration_template(rails: rails_project))
|
|
77
|
+
|
|
78
|
+
[true, target]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -11,6 +11,7 @@ module Tng
|
|
|
11
11
|
class TestGenerator
|
|
12
12
|
GENERATE_TESTS_PATH = "cli/tng_rails/contents/generate_tests"
|
|
13
13
|
CONTENT_RESPONSES_PATH = "cli/tng_rails/content_responses"
|
|
14
|
+
RUBY_CONTENT_RESPONSES_PATH = "cli/tng_ruby/content_responses"
|
|
14
15
|
POLL_INTERVAL_SECONDS = 5 # Poll every 5 seconds
|
|
15
16
|
MAX_POLL_DURATION_SECONDS = 420 # 7 minutes total
|
|
16
17
|
|
|
@@ -36,6 +37,10 @@ module Tng
|
|
|
36
37
|
generate_test_for_type(other_file, method_info, :other, progress: progress)
|
|
37
38
|
end
|
|
38
39
|
|
|
40
|
+
def run_for_ruby_method(other_file, method_info, progress: nil)
|
|
41
|
+
generate_test_for_ruby(other_file, method_info, progress: progress)
|
|
42
|
+
end
|
|
43
|
+
|
|
39
44
|
|
|
40
45
|
# Audit mode - async, polls for completion
|
|
41
46
|
def run_audit_for_controller_method(controller, method_info, progress: nil)
|
|
@@ -54,6 +59,10 @@ module Tng
|
|
|
54
59
|
run_audit_for_type(other_file, method_info, :other, progress: progress)
|
|
55
60
|
end
|
|
56
61
|
|
|
62
|
+
def run_audit_for_ruby_method(other_file, method_info, progress: nil)
|
|
63
|
+
run_audit_for_ruby(other_file, method_info, progress: progress)
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
# X-Ray mode - async, polls for completion
|
|
58
67
|
def run_xray_for_controller_method(controller, method_info, progress: nil)
|
|
59
68
|
run_xray_for_type(controller, method_info, :controller, progress: progress)
|
|
@@ -71,6 +80,10 @@ module Tng
|
|
|
71
80
|
run_xray_for_type(other_file, method_info, :other, progress: progress)
|
|
72
81
|
end
|
|
73
82
|
|
|
83
|
+
def run_xray_for_ruby_method(other_file, method_info, progress: nil)
|
|
84
|
+
run_xray_for_ruby(other_file, method_info, progress: progress)
|
|
85
|
+
end
|
|
86
|
+
|
|
74
87
|
def run_audit_for_type(file_object, method_info, type, progress: nil)
|
|
75
88
|
response = send_audit_request_for_type(file_object, method_info, type)
|
|
76
89
|
return { error: :network_error, message: "Network error" } unless response
|
|
@@ -90,7 +103,7 @@ module Tng
|
|
|
90
103
|
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
91
104
|
|
|
92
105
|
# Poll for completion (audit only)
|
|
93
|
-
result = poll_for_completion(job_id, progress: progress)
|
|
106
|
+
result = poll_for_completion(job_id, progress: progress, content_path: CONTENT_RESPONSES_PATH)
|
|
94
107
|
return { error: :timeout, message: "Audit timed out" } unless result
|
|
95
108
|
|
|
96
109
|
# Audit results come back as the result directly (not wrapped)
|
|
@@ -104,17 +117,18 @@ module Tng
|
|
|
104
117
|
def send_audit_request_for_type(file_object, method_info, type)
|
|
105
118
|
config = request_config
|
|
106
119
|
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
120
|
+
enriched_method_info = enrich_method_info_with_trace_v2(file_object, method_info, ruby_mode: false)
|
|
107
121
|
|
|
108
122
|
# Pass audit_mode: true as 8th parameter
|
|
109
123
|
case type
|
|
110
124
|
when :controller
|
|
111
|
-
Tng.send_request_for_controller(name, file_object[:path],
|
|
125
|
+
Tng.send_request_for_controller(name, file_object[:path], enriched_method_info, *config, true, false)
|
|
112
126
|
when :model
|
|
113
|
-
Tng.send_request_for_model(name, file_object[:path],
|
|
127
|
+
Tng.send_request_for_model(name, file_object[:path], enriched_method_info, *config, true, false)
|
|
114
128
|
when :service
|
|
115
|
-
Tng.send_request_for_service(name, file_object[:path],
|
|
129
|
+
Tng.send_request_for_service(name, file_object[:path], enriched_method_info, *config, true, false)
|
|
116
130
|
when :other
|
|
117
|
-
Tng.send_request_for_other(name, file_object[:path],
|
|
131
|
+
Tng.send_request_for_other(name, file_object[:path], enriched_method_info, *config, true, false)
|
|
118
132
|
end
|
|
119
133
|
end
|
|
120
134
|
|
|
@@ -137,7 +151,7 @@ module Tng
|
|
|
137
151
|
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
138
152
|
|
|
139
153
|
# Poll for completion (similar to audit)
|
|
140
|
-
result = poll_for_completion(job_id, progress: progress)
|
|
154
|
+
result = poll_for_completion(job_id, progress: progress, content_path: CONTENT_RESPONSES_PATH)
|
|
141
155
|
return { error: :timeout, message: "X-Ray generation timed out" } unless result
|
|
142
156
|
|
|
143
157
|
# X-Ray results should contain mermaid_code and explanation
|
|
@@ -150,17 +164,18 @@ module Tng
|
|
|
150
164
|
def send_xray_request_for_type(file_object, method_info, type)
|
|
151
165
|
config = request_config
|
|
152
166
|
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
167
|
+
enriched_method_info = enrich_method_info_with_trace_v2(file_object, method_info, ruby_mode: false)
|
|
153
168
|
|
|
154
169
|
# Pass xray_mode: true as 9th parameter (audit_mode: false, xray_mode: true)
|
|
155
170
|
case type
|
|
156
171
|
when :controller
|
|
157
|
-
Tng.send_request_for_controller(name, file_object[:path],
|
|
172
|
+
Tng.send_request_for_controller(name, file_object[:path], enriched_method_info, *config, false, true)
|
|
158
173
|
when :model
|
|
159
|
-
Tng.send_request_for_model(name, file_object[:path],
|
|
174
|
+
Tng.send_request_for_model(name, file_object[:path], enriched_method_info, *config, false, true)
|
|
160
175
|
when :service
|
|
161
|
-
Tng.send_request_for_service(name, file_object[:path],
|
|
176
|
+
Tng.send_request_for_service(name, file_object[:path], enriched_method_info, *config, false, true)
|
|
162
177
|
when :other
|
|
163
|
-
Tng.send_request_for_other(name, file_object[:path],
|
|
178
|
+
Tng.send_request_for_other(name, file_object[:path], enriched_method_info, *config, false, true)
|
|
164
179
|
end
|
|
165
180
|
end
|
|
166
181
|
|
|
@@ -186,7 +201,7 @@ module Tng
|
|
|
186
201
|
|
|
187
202
|
return unless job_id
|
|
188
203
|
|
|
189
|
-
result = poll_for_completion(job_id, progress: progress)
|
|
204
|
+
result = poll_for_completion(job_id, progress: progress, content_path: CONTENT_RESPONSES_PATH)
|
|
190
205
|
|
|
191
206
|
return unless result
|
|
192
207
|
|
|
@@ -196,7 +211,7 @@ module Tng
|
|
|
196
211
|
file_result = Tng::Utils.save_test_file(result.to_json)
|
|
197
212
|
return unless file_result
|
|
198
213
|
|
|
199
|
-
trigger_cleanup(job_id)
|
|
214
|
+
trigger_cleanup(job_id, content_path: CONTENT_RESPONSES_PATH)
|
|
200
215
|
|
|
201
216
|
file_result.merge(generation_time: generation_time)
|
|
202
217
|
end
|
|
@@ -204,20 +219,138 @@ module Tng
|
|
|
204
219
|
def send_request_for_type(file_object, method_info, type)
|
|
205
220
|
config = request_config
|
|
206
221
|
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
222
|
+
enriched_method_info = enrich_method_info_with_trace_v2(file_object, method_info, ruby_mode: false)
|
|
207
223
|
|
|
208
224
|
# Pass audit_mode: false as 8th parameter for normal test generation
|
|
209
225
|
case type
|
|
210
226
|
when :controller
|
|
211
|
-
Tng.send_request_for_controller(name, file_object[:path],
|
|
227
|
+
Tng.send_request_for_controller(name, file_object[:path], enriched_method_info, *config, false, false)
|
|
212
228
|
when :model
|
|
213
|
-
Tng.send_request_for_model(name, file_object[:path],
|
|
229
|
+
Tng.send_request_for_model(name, file_object[:path], enriched_method_info, *config, false, false)
|
|
214
230
|
when :service
|
|
215
|
-
Tng.send_request_for_service(name, file_object[:path],
|
|
231
|
+
Tng.send_request_for_service(name, file_object[:path], enriched_method_info, *config, false, false)
|
|
216
232
|
when :other
|
|
217
|
-
Tng.send_request_for_other(name, file_object[:path],
|
|
233
|
+
Tng.send_request_for_other(name, file_object[:path], enriched_method_info, *config, false, false)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def generate_test_for_ruby(file_object, method_info, progress: nil)
|
|
238
|
+
start_time = Time.now
|
|
239
|
+
|
|
240
|
+
response = send_request_for_ruby(file_object, method_info, audit_mode: false, xray_mode: false)
|
|
241
|
+
return unless response
|
|
242
|
+
|
|
243
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
244
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
job_data = JSON.parse(response.body)
|
|
248
|
+
return { error: :auth_failed, message: "Invalid or missing API key" } if response.status == 401
|
|
249
|
+
return { error: :auth_failed, message: "API key expired or usage limit reached" } if response.status == 403
|
|
250
|
+
|
|
251
|
+
job_id = job_data["job_id"]
|
|
252
|
+
return unless job_id
|
|
253
|
+
|
|
254
|
+
result = poll_for_completion(job_id, progress: progress, content_path: RUBY_CONTENT_RESPONSES_PATH)
|
|
255
|
+
return unless result
|
|
256
|
+
|
|
257
|
+
end_time = Time.now
|
|
258
|
+
generation_time = end_time - start_time
|
|
259
|
+
|
|
260
|
+
file_result = Tng::Utils.save_test_file(result.to_json)
|
|
261
|
+
return unless file_result
|
|
262
|
+
|
|
263
|
+
trigger_cleanup(job_id, content_path: RUBY_CONTENT_RESPONSES_PATH)
|
|
264
|
+
|
|
265
|
+
file_result.merge(generation_time: generation_time)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def run_audit_for_ruby(file_object, method_info, progress: nil)
|
|
269
|
+
response = send_request_for_ruby(file_object, method_info, audit_mode: true, xray_mode: false)
|
|
270
|
+
return { error: :network_error, message: "Network error" } unless response
|
|
271
|
+
|
|
272
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
273
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
return { error: :auth_failed, message: "Invalid or missing API key" } if response.status == 401
|
|
277
|
+
return { error: :auth_failed, message: "API key expired" } if response.status == 403
|
|
278
|
+
|
|
279
|
+
begin
|
|
280
|
+
job_data = JSON.parse(response.body)
|
|
281
|
+
return { error: :server_error, message: job_data["error"] } if job_data["error"]
|
|
282
|
+
|
|
283
|
+
job_id = job_data["job_id"]
|
|
284
|
+
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
285
|
+
|
|
286
|
+
result = poll_for_completion(job_id, progress: progress, content_path: RUBY_CONTENT_RESPONSES_PATH)
|
|
287
|
+
return { error: :timeout, message: "Audit timed out" } unless result
|
|
288
|
+
|
|
289
|
+
{ audit_results: result }
|
|
290
|
+
rescue JSON::ParserError => e
|
|
291
|
+
{ error: :parse_error, message: "Failed to parse audit response: #{e.message}" }
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def run_xray_for_ruby(file_object, method_info, progress: nil)
|
|
296
|
+
response = send_request_for_ruby(file_object, method_info, audit_mode: false, xray_mode: true)
|
|
297
|
+
return { error: :network_error, message: "Network error" } unless response
|
|
298
|
+
|
|
299
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
300
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
return { error: :auth_failed, message: "Invalid or missing API key" } if response.status == 401
|
|
304
|
+
return { error: :auth_failed, message: "API key expired" } if response.status == 403
|
|
305
|
+
|
|
306
|
+
begin
|
|
307
|
+
job_data = JSON.parse(response.body)
|
|
308
|
+
return { error: :server_error, message: job_data["error"] } if job_data["error"]
|
|
309
|
+
|
|
310
|
+
job_id = job_data["job_id"]
|
|
311
|
+
return { error: :server_error, message: "No job_id returned" } unless job_id
|
|
312
|
+
|
|
313
|
+
result = poll_for_completion(job_id, progress: progress, content_path: RUBY_CONTENT_RESPONSES_PATH)
|
|
314
|
+
return { error: :timeout, message: "X-Ray generation timed out" } unless result
|
|
315
|
+
|
|
316
|
+
result
|
|
317
|
+
rescue JSON::ParserError => e
|
|
318
|
+
{ error: :parse_error, message: "Failed to parse X-Ray response: #{e.message}" }
|
|
218
319
|
end
|
|
219
320
|
end
|
|
220
321
|
|
|
322
|
+
def send_request_for_ruby(file_object, method_info, audit_mode:, xray_mode:)
|
|
323
|
+
config = request_config
|
|
324
|
+
name = file_object[:name] || File.basename(file_object[:path], ".rb")
|
|
325
|
+
enriched_method_info = enrich_method_info_with_trace_v2(file_object, method_info, ruby_mode: true)
|
|
326
|
+
|
|
327
|
+
Tng.send_request_for_ruby(name, file_object[:path], enriched_method_info, *config, audit_mode, xray_mode)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def enrich_method_info_with_trace_v2(file_object, method_info, ruby_mode:)
|
|
331
|
+
return method_info unless method_info.is_a?(Hash)
|
|
332
|
+
return method_info if method_info.key?(:trace_v2) || method_info.key?("trace_v2")
|
|
333
|
+
|
|
334
|
+
method_name = method_info[:name] || method_info["name"]
|
|
335
|
+
return method_info if method_name.to_s.empty?
|
|
336
|
+
|
|
337
|
+
class_name = method_info[:class_name] || method_info["class_name"]
|
|
338
|
+
project_root = Dir.pwd
|
|
339
|
+
path = File.expand_path(file_object[:path])
|
|
340
|
+
|
|
341
|
+
trace_v2 =
|
|
342
|
+
if ruby_mode
|
|
343
|
+
Tng::Analyzer::Context.analyze_symbolic_trace_ruby_v2(project_root, path, method_name, class_name)
|
|
344
|
+
else
|
|
345
|
+
Tng::Analyzer::Context.analyze_symbolic_trace_v2(project_root, path, method_name, class_name)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
method_info.merge(trace_v2: trace_v2)
|
|
349
|
+
rescue StandardError => e
|
|
350
|
+
debug_log("Trace v2 enrichment failed for #{file_object[:path]}##{method_name}: #{e.message}") if debug_enabled?
|
|
351
|
+
method_info
|
|
352
|
+
end
|
|
353
|
+
|
|
221
354
|
def request_config
|
|
222
355
|
[
|
|
223
356
|
Tng::Utils.fixture_content,
|
|
@@ -228,8 +361,8 @@ module Tng
|
|
|
228
361
|
end
|
|
229
362
|
|
|
230
363
|
# Modified method signature and added interrupt handler
|
|
231
|
-
def poll_for_completion(job_id, progress: nil)
|
|
232
|
-
start_time = Time.
|
|
364
|
+
def poll_for_completion(job_id, progress: nil, content_path: CONTENT_RESPONSES_PATH)
|
|
365
|
+
start_time = Time.now
|
|
233
366
|
attempts = 0
|
|
234
367
|
max_attempts = MAX_POLL_DURATION_SECONDS / POLL_INTERVAL_SECONDS
|
|
235
368
|
|
|
@@ -238,7 +371,7 @@ module Tng
|
|
|
238
371
|
|
|
239
372
|
loop do
|
|
240
373
|
attempts += 1
|
|
241
|
-
seconds_elapsed = (Time.
|
|
374
|
+
seconds_elapsed = (Time.now - start_time).to_i
|
|
242
375
|
|
|
243
376
|
if seconds_elapsed > MAX_POLL_DURATION_SECONDS
|
|
244
377
|
return { error: :timeout, message: "Test generation timed out after #{MAX_POLL_DURATION_SECONDS} seconds" }
|
|
@@ -256,7 +389,7 @@ module Tng
|
|
|
256
389
|
|
|
257
390
|
sleep(POLL_INTERVAL_SECONDS)
|
|
258
391
|
|
|
259
|
-
status_response = @http_client.get("#{
|
|
392
|
+
status_response = @http_client.get("#{content_path}/#{job_id}")
|
|
260
393
|
|
|
261
394
|
if status_response.is_a?(HTTPX::ErrorResponse)
|
|
262
395
|
debug_log("Status check failed: #{status_response.error&.message}") if debug_enabled?
|
|
@@ -370,8 +503,8 @@ module Tng
|
|
|
370
503
|
end
|
|
371
504
|
end
|
|
372
505
|
|
|
373
|
-
def trigger_cleanup(job_id)
|
|
374
|
-
@http_client.patch("#{
|
|
506
|
+
def trigger_cleanup(job_id, content_path: CONTENT_RESPONSES_PATH)
|
|
507
|
+
@http_client.patch("#{content_path}/#{job_id}/cleanup")
|
|
375
508
|
rescue StandardError => e
|
|
376
509
|
debug_log("Cleanup request failed: #{e.message}") if debug_enabled?
|
|
377
510
|
end
|
data/lib/tng/services/testng.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Services
|
|
2
2
|
class Testng
|
|
3
3
|
STATS_PATH = "cli/tng_rails/stats"
|
|
4
|
+
RUBY_STATS_PATH = "cli/tng_ruby/stats"
|
|
4
5
|
|
|
5
6
|
def initialize(http_client)
|
|
6
7
|
@http_client = http_client
|
|
@@ -78,7 +79,8 @@ module Services
|
|
|
78
79
|
"User-Agent" => "Tng/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
path = Tng::Utils.rails_project?(Dir.pwd) ? STATS_PATH : RUBY_STATS_PATH
|
|
83
|
+
response = @http_client.get(path, headers: headers)
|
|
82
84
|
|
|
83
85
|
return if response.is_a?(HTTPX::ErrorResponse)
|
|
84
86
|
|