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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
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
- remove_file initializer_path
17
- create_file initializer_path, configuration_template
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
- create_file initializer_path, configuration_template
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
@@ -70,8 +70,6 @@ module Tng
70
70
  response
71
71
  end
72
72
 
73
- # Verify if the API key is valid
74
- # Returns: { valid: true/false, error: nil/String }
75
73
  def verify_auth
76
74
  response = get("cli/tng_rails/stats")
77
75
 
@@ -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
- audit_results = result[:audit_results]
22
- return unless audit_results
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
- rails_root = defined?(Rails) && Rails.root ? Rails.root.to_s : Dir.pwd
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
- require 'active_support/core_ext/string/inflections'
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
- rails_root = defined?(Rails) && Rails.root ? Rails.root.to_s : Dir.pwd
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], method_info, *config, true, false)
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], method_info, *config, true, false)
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], method_info, *config, true, false)
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], method_info, *config, true, false)
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], method_info, *config, false, true)
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], method_info, *config, false, true)
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], method_info, *config, false, true)
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], method_info, *config, false, true)
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], method_info, *config, false, false)
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], method_info, *config, false, false)
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], method_info, *config, false, false)
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], method_info, *config, false, false)
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.current
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.current - start_time).to_i
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("#{CONTENT_RESPONSES_PATH}/#{job_id}")
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("#{CONTENT_RESPONSES_PATH}/#{job_id}/cleanup")
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
@@ -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
- response = @http_client.get(STATS_PATH, headers: headers)
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