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 +4 -4
- data/README.md +0 -7
- data/bin/tng +12 -16
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/generators/tng/install_generator.rb +160 -2
- data/lib/tng/api/http_client.rb +13 -0
- data/lib/tng/services/test_generator.rb +8 -0
- data/lib/tng/version.rb +1 -1
- data/lib/tng.rb +10 -19
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39e06ff3d2ea9fdf295f87ac41e01e3fef4365899a128d7e013d913c568253f4
|
|
4
|
+
data.tar.gz: dd42840fd11e8c165b9777e540ae85c5eaa7047fd3b3923299cab3392431d0ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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=#{
|
|
137
|
+
normalized << "--file=#{::Regexp.last_match(2)}"
|
|
138
138
|
when /^(?:--)?(method|m)=(.+)$/
|
|
139
|
-
normalized << "--method=#{
|
|
139
|
+
normalized << "--method=#{::Regexp.last_match(2)}"
|
|
140
140
|
when /^(help|h)=(.+)$/
|
|
141
|
-
normalized << "--help=#{
|
|
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
|
-
|
|
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?(
|
|
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
|
{
|
data/lib/tng/api/http_client.rb
CHANGED
|
@@ -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
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.
|
|
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.
|
|
290
|
+
v0.2.8 ┘\n"
|
|
291
291
|
rdoc_options: []
|
|
292
292
|
require_paths:
|
|
293
293
|
- lib
|