tng 0.2.6 → 0.2.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32a8b662c4dfe266510a9191d5cb6fbb2b0d52372db4e020884967efeb761c52
4
- data.tar.gz: f91b5768b12f8715fc400cef1da192d1a5b9e2ca7c941095f108b438e1674499
3
+ metadata.gz: 39e06ff3d2ea9fdf295f87ac41e01e3fef4365899a128d7e013d913c568253f4
4
+ data.tar.gz: dd42840fd11e8c165b9777e540ae85c5eaa7047fd3b3923299cab3392431d0ee
5
5
  SHA512:
6
- metadata.gz: 670688569bc80522d0a55b1c1625428443a015d1a1432dd8ce1851e6d7ca30dbfa528cee00d961a6b67ffeb9493678c34678766541de16596571e07448a52e1a
7
- data.tar.gz: 9bce7c524936fcf7d8c8a06f7c55c546c7ea4e1757034e7812c3eb721a363bade6086b3e16ac97db64ffce1399d5c1df4c9702c90bfd656369a4456895621535
6
+ metadata.gz: 188224ee7db41585b1f4bf6737a549ba6a2b7830b07b8ba31959a98b930901f07979a1e321d00fc129dd82321c8c35686571462c152534c1f7537f5c2f4f563c
7
+ data.tar.gz: 0c5f179a3a833042e48cf06df02bf878e0ab7478975bbe328920831994d1097cd4fbf093bb3e8f6be236a7e172027d704f5746fc2e8272179e475b0132622621
data/README.md CHANGED
@@ -124,9 +124,6 @@ Tng.configure do |config|
124
124
  config.api_key = ENV["TNG_API_KEY"]
125
125
  config.base_url = "https://app.tng.sh/"
126
126
 
127
- config.read_source_code = true
128
- config.read_test_code = true
129
-
130
127
  config.testing_framework = "rspec"
131
128
  config.assertion_style = "expect"
132
129
  config.describe_style = true
@@ -189,10 +186,6 @@ end
189
186
  Tng.configure do |config|
190
187
  config.api_key = ENV["TNG_API_KEY"]
191
188
  config.base_url = "https://app.tng.sh/"
192
-
193
- config.read_source_code = true
194
- config.read_test_code = true
195
-
196
189
  config.testing_framework = "rspec"
197
190
  config.assertion_style = "expect"
198
191
  config.let_style = true
data/bin/tng CHANGED
@@ -130,15 +130,15 @@ class CLI
130
130
  def preprocess_arguments(argv)
131
131
  normalized = []
132
132
  positional_args = []
133
-
133
+
134
134
  argv.each_with_index do |arg, index|
135
135
  case arg
136
136
  when /^(?:--)?(file|f)=(.+)$/
137
- normalized << "--file=#{$2}"
137
+ normalized << "--file=#{::Regexp.last_match(2)}"
138
138
  when /^(?:--)?(method|m)=(.+)$/
139
- normalized << "--method=#{$2}"
139
+ normalized << "--method=#{::Regexp.last_match(2)}"
140
140
  when /^(help|h)=(.+)$/
141
- normalized << "--help=#{$2}"
141
+ normalized << "--help=#{::Regexp.last_match(2)}"
142
142
  when /^--file$/, /^-f$/
143
143
  normalized << arg
144
144
  when /^--method$/, /^-m$/
@@ -156,29 +156,25 @@ class CLI
156
156
  end
157
157
  end
158
158
  end
159
-
159
+
160
160
  if positional_args.length >= 2
161
161
  has_file = normalized.any? { |a| a.match?(/^--file/) }
162
162
  has_method = normalized.any? { |a| a.match?(/^--method/) }
163
-
164
- unless has_file
165
- normalized << "--file=#{positional_args[0]}"
166
- end
167
- unless has_method
168
- normalized << "--method=#{positional_args[1]}"
169
- end
163
+
164
+ normalized << "--file=#{positional_args[0]}" unless has_file
165
+ normalized << "--method=#{positional_args[1]}" unless has_method
170
166
  elsif positional_args.length == 1
171
167
  arg = positional_args[0]
172
168
  has_file = normalized.any? { |a| a.match?(/^--file/) }
173
169
  has_method = normalized.any? { |a| a.match?(/^--method/) }
174
-
175
- if !has_file && (arg.end_with?('.rb') || arg.include?('/'))
170
+
171
+ if !has_file && (arg.end_with?(".rb") || arg.include?("/"))
176
172
  normalized << "--file=#{arg}"
177
- elsif !has_method && !arg.include?('/')
173
+ elsif !has_method && !arg.include?("/")
178
174
  normalized << "--method=#{arg}"
179
175
  end
180
176
  end
181
-
177
+
182
178
  normalized
183
179
  end
184
180
 
data/binaries/tng.bundle CHANGED
Binary file
data/binaries/tng.so CHANGED
Binary file
@@ -3,6 +3,7 @@
3
3
  require "rails/generators"
4
4
  require "yaml"
5
5
  require "tng"
6
+ require "pathname"
6
7
 
7
8
  module Tng
8
9
  module Generators
@@ -18,6 +19,7 @@ module Tng
18
19
  @authentication_library = detect_authentication_library
19
20
  @authz_library = detect_authorization_library
20
21
  @factory_library = detect_factory_library
22
+ @test_examples = detect_test_examples
21
23
 
22
24
  initializer_path = "config/initializers/tng.rb"
23
25
 
@@ -61,6 +63,8 @@ module Tng
61
63
  factory_lib = "config.factory_library = \"#{@factory_library}\""
62
64
 
63
65
  framework_specific = generate_framework_specific_config(@test_framework, framework_config)
66
+
67
+ test_examples_config = @test_examples.any? ? @test_examples.inspect : "[]"
64
68
  [
65
69
  "# frozen_string_literal: true",
66
70
  "",
@@ -70,8 +74,6 @@ module Tng
70
74
  " config.api_key = nil",
71
75
  " # You dont need to change this url, unless you will instructed by the CLI.",
72
76
  " config.base_url = \"https://app.tng.sh/\"",
73
- " config.read_source_code = true # Options: true, false",
74
- " config.read_test_code = true # Options: true, false",
75
77
  "",
76
78
  " # Testing Framework",
77
79
  " config.testing_framework = \"#{@test_framework}\" # Options: minitest, rspec",
@@ -80,6 +82,12 @@ module Tng
80
82
  " config.http_mock_library = \"#{framework_config["http_mock_library"]}\" # Options: webmock, vcr, httparty, nil",
81
83
  " #{factory_lib} # Options: factory_bot, factory_girl, fabrication, fabricator, fixtures, active_record",
82
84
  "",
85
+ " # Test Examples",
86
+ " # Example test files for LLM to learn patterns and reduce hallucinations",
87
+ " # Format: [{{\"name\" => \"test_name\", \"path\" => \"spec/models/user_spec.rb\"}}]",
88
+ " # Leave empty [] to auto-detect from project",
89
+ " config.test_examples = #{test_examples_config}",
90
+ "",
83
91
  " # Authentication#{auth_comment}",
84
92
  " #{auth_enabled} # Options: true, false",
85
93
  " #{auth_lib} # Options: devise, clearance, sorcery, nil",
@@ -190,6 +198,156 @@ module Tng
190
198
  "active_record"
191
199
  end
192
200
 
201
+ def detect_test_examples
202
+ rails_root = defined?(Rails) && Rails.root ? Rails.root.to_s : Dir.pwd
203
+ examples = []
204
+
205
+ # Determine test directory based on framework
206
+ test_dir = @test_framework == "rspec" ? "spec" : "test"
207
+
208
+ # Comprehensive exclusion list for build artifacts, caches, dependencies
209
+ exclude_dirs = [
210
+ ".git", "log", "tmp", "vendor", "node_modules",
211
+ "coverage", "public", "storage", "db", "config",
212
+ "lib/tasks", "bin", ".bundle", ".vscode", ".idea"
213
+ ]
214
+
215
+ # Also exclude files larger than 500KB (probably not good examples)
216
+ max_file_size = 500 * 1024 # 500KB
217
+
218
+ # Find all test directories and subdirectories
219
+ test_directories = find_test_directories(rails_root, test_dir, exclude_dirs)
220
+
221
+ # Determine collection strategy based on folder structure
222
+ main_test_dir = File.join(rails_root, test_dir)
223
+ has_subfolders = test_directories.size > 1
224
+
225
+ if has_subfolders
226
+ # If we have subfolders, take 2 files per directory
227
+ collected_files = []
228
+ test_directories.each do |dir_path|
229
+ dir_files = find_valid_test_files_in_directory(dir_path, max_file_size)
230
+ # Take up to 2 files from this directory
231
+ collected_files.concat(dir_files.first(2))
232
+ end
233
+ else
234
+ # If no subfolders, take up to 5 files from main test directory
235
+ collected_files = find_valid_test_files_in_directory(main_test_dir, max_file_size).first(5)
236
+ end
237
+
238
+ # Sort all collected files by modification time (recent first), then by size (smaller files first)
239
+ collected_files.sort_by! { |f| [-File.mtime(f).to_i, File.size(f)] }
240
+
241
+ # Convert to examples format
242
+ collected_files.first(5).each do |test_file|
243
+ examples << {
244
+ "name" => File.basename(test_file),
245
+ "path" => Pathname.new(test_file).relative_path_from(Pathname.new(rails_root)).to_s
246
+ }
247
+ rescue StandardError
248
+ next
249
+ end
250
+
251
+ examples
252
+ rescue StandardError
253
+ # Return empty array if anything goes wrong
254
+ []
255
+ end
256
+
257
+ def find_test_directories(rails_root, test_dir, exclude_dirs)
258
+ directories = []
259
+ main_test_path = File.join(rails_root, test_dir)
260
+
261
+ return [] unless Dir.exist?(main_test_path)
262
+
263
+ # Add main test directory
264
+ directories << main_test_path
265
+
266
+ # Find all subdirectories recursively
267
+ Dir.glob(File.join(main_test_path, "**/*/")).each do |dir|
268
+ # Skip excluded directories
269
+ next if exclude_dirs.any? { |excluded| dir.include?("/#{excluded}/") }
270
+
271
+ # Skip if it's not a directory with actual test files
272
+ next unless test_files?(dir)
273
+
274
+ directories << dir
275
+ end
276
+
277
+ directories.uniq
278
+ end
279
+
280
+ def test_files?(dir_path)
281
+ return false unless Dir.exist?(dir_path)
282
+
283
+ pattern = @test_framework == "rspec" ? "*_spec.rb" : "*_test.rb"
284
+ Dir.glob(File.join(dir_path, pattern)).any?
285
+ end
286
+
287
+ def find_valid_test_files_in_directory(dir_path, max_file_size)
288
+ return [] unless Dir.exist?(dir_path)
289
+
290
+ pattern = @test_framework == "rspec" ? "*_spec.rb" : "*_test.rb"
291
+ files = []
292
+
293
+ Dir.glob(File.join(dir_path, pattern)).each do |test_file|
294
+ next if File.size(test_file) > max_file_size
295
+ next unless valid_test_file?(test_file)
296
+
297
+ files << test_file
298
+ rescue StandardError
299
+ next
300
+ end
301
+
302
+ files
303
+ end
304
+
305
+ def valid_test_file?(file_path)
306
+ return false unless File.exist?(file_path) && File.readable?(file_path)
307
+ return false if File.size(file_path) == 0
308
+
309
+ begin
310
+ # Read first 1KB of the file
311
+ content = File.read(file_path, 1024)
312
+
313
+ # Check for Ruby test indicators based on framework
314
+ test_indicators = if @test_framework == "rspec"
315
+ [
316
+ "describe ", # RSpec describe blocks
317
+ "context ", # RSpec context blocks
318
+ "it ", # RSpec examples
319
+ "specify ", # RSpec specify
320
+ "expect(", # RSpec expectations
321
+ "before(", # RSpec hooks
322
+ "let(", # RSpec let
323
+ "subject ", # RSpec subject
324
+ 'require "spec_helper"', # RSpec spec helper
325
+ 'require "rails_helper"' # RSpec rails helper
326
+ ]
327
+ else # minitest
328
+ [
329
+ "def test_", # Minitest test methods
330
+ "class.*Test", # Test classes
331
+ "assert ", # Assertions
332
+ "refute ", # Refutations
333
+ 'require "test_helper"', # Minitest helper
334
+ 'require "minitest"', # Minitest require
335
+ "MiniTest::Test" # Minitest base class
336
+ ]
337
+ end
338
+
339
+ # Must contain at least one test indicator
340
+ return false unless test_indicators.any? { |indicator| content.include?(indicator) }
341
+
342
+ # Basic Ruby syntax check - should not have obvious non-Ruby content
343
+ return false if content.scan(/#!/).size > 5 || content.include?("<?xml")
344
+
345
+ true
346
+ rescue StandardError
347
+ false
348
+ end
349
+ end
350
+
193
351
  def generate_framework_config(framework)
194
352
  if framework == "minitest"
195
353
  {
@@ -51,6 +51,19 @@ module Tng
51
51
  response
52
52
  end
53
53
 
54
+ def patch(path, payload: {}, headers: {})
55
+ merged_headers = json_default_headers.merge(headers)
56
+
57
+ response = HTTPX.with(timeout: @timeout).patch(
58
+ "#{@api_endpoint}/#{path}",
59
+ json: payload,
60
+ headers: merged_headers
61
+ )
62
+
63
+ debug_response("PATCH #{path}", response) if debug_enabled?
64
+ response
65
+ end
66
+
54
67
  def ping
55
68
  response = HTTPX.with(timeout: @timeout).get("#{@api_endpoint}/ping")
56
69
  debug_response("GET /ping", response) if debug_enabled?
@@ -76,6 +76,8 @@ module Tng
76
76
  file_result = Tng::Utils.save_test_file(result.to_json)
77
77
  return unless file_result
78
78
 
79
+ trigger_cleanup(job_id)
80
+
79
81
  file_result.merge(generation_time: generation_time)
80
82
  end
81
83
 
@@ -204,6 +206,12 @@ module Tng
204
206
  end
205
207
  end
206
208
 
209
+ def trigger_cleanup(job_id)
210
+ @http_client.patch("#{CONTENT_RESPONSES_PATH}/#{job_id}/cleanup")
211
+ rescue StandardError => e
212
+ debug_log("Cleanup request failed: #{e.message}") if debug_enabled?
213
+ end
214
+
207
215
  def debug_enabled?
208
216
  ENV["DEBUG"] == "1"
209
217
  end
data/lib/tng/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tng
4
- VERSION = "0.2.6"
4
+ VERSION = "0.2.8"
5
5
  end
data/lib/tng.rb CHANGED
@@ -53,8 +53,6 @@ module Tng
53
53
  @config = {
54
54
  api_key: nil,
55
55
  base_url: "https://app.tng.sh/",
56
- read_source_code: true,
57
- read_test_code: true,
58
56
  testing_framework: "minitest",
59
57
  authentication_enabled: false,
60
58
  authorization_library: nil,
@@ -62,7 +60,8 @@ module Tng
62
60
  authentication_methods: [],
63
61
  mock_library: "minitest/mock",
64
62
  http_mock_library: "webmock",
65
- factory_library: "active_record"
63
+ factory_library: "active_record",
64
+ test_examples: []
66
65
  }
67
66
 
68
67
  def self.configure
@@ -89,22 +88,6 @@ module Tng
89
88
  @config[:base_url]
90
89
  end
91
90
 
92
- def self.read_source_code=(value)
93
- @config[:read_source_code] = value
94
- end
95
-
96
- def self.read_source_code
97
- @config[:read_source_code]
98
- end
99
-
100
- def self.read_test_code=(value)
101
- @config[:read_test_code] = value
102
- end
103
-
104
- def self.read_test_code
105
- @config[:read_test_code]
106
- end
107
-
108
91
  def self.testing_framework=(value)
109
92
  @config[:testing_framework] = value
110
93
  initialize_framework_defaults(value)
@@ -258,6 +241,14 @@ module Tng
258
241
  @config[:subject_style]
259
242
  end
260
243
 
244
+ def self.test_examples=(value)
245
+ @config[:test_examples] = value
246
+ end
247
+
248
+ def self.test_examples
249
+ @config[:test_examples]
250
+ end
251
+
261
252
  def self.authentication_configured?
262
253
  return false unless authentication_enabled
263
254
  return false if authentication_methods.nil? || authentication_methods.empty?
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.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - ralucab
@@ -287,7 +287,7 @@ post_install_message: "┌ TNG ────────────────
287
287
  \ │\n│ • bundle exec
288
288
  tng --help - Show help information │\n│ │\n│
289
289
  \ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
290
- v0.2.6 ┘\n"
290
+ v0.2.8 ┘\n"
291
291
  rdoc_options: []
292
292
  require_paths:
293
293
  - lib